在C中包含有符号和无符号变量的解释?
我在C规范中读到了一点,无符号变量(特别是unsigned short int )在整数溢出上执行一些所谓的回绕,尽管除了我留下未定义的行为之外我找不到任何有符号的变量。 我的教授告诉我他们的价值观也被包围了。(也许他只是意味着gcc)我认为这些比特被截断了,我留下的位给了我一些奇怪的价值?! 那么,任何人都可以解释什么是环绕的,它与截断位有什么不同。
有符号整数变量在C语言中没有环绕行为。 算术计算期间的有符号整数溢出会产生未定义的行为 。 请注意,您提到的GCC编译器在优化中实现严格溢出语义是已知的,这意味着它利用了这种未定义行为情况提供的自由:GCC编译器假定有符号整数值永远不会回绕。 这意味着GCC实际上恰好是您不能依赖有符号整数类型的环绕行为的编译器之一。
例如,GCC编译器可以假设对于变量int i
具有以下条件
if (i > 0 && i + 1 > 0)
相当于一个人
if (i > 0)
这正是严格溢出语义的含义。
无符号整数类型实现模运算。 模数等于2^N
,其中N
是类型的值表示中的位数。 因此,无符号整数类型确实会出现溢出。
但是,C语言从不在小于int
/ unsigned int
域中执行算术计算。 您在问题中提到的类型unsigned short int
通常会在任何计算开始之前被提升为表达式中的int
类型(假设unsigned short
的范围符合int
的范围)。 这意味着1)使用unsigned short int
的计算将在int
的域中执行,当int
溢出时发生溢出,2)在这样的计算期间溢出将导致未定义的行为,而不是环绕行为。
例如,此代码生成环绕
unsigned i = USHRT_MAX; i *= INT_MAX; /* <- unsigned arithmetic, overflows, wraps around */
而这段代码
unsigned short i = USHRT_MAX; i *= INT_MAX; /* <- signed arithmetic, overflows, produces undefined behavior */
导致未定义的行为。
如果没有发生int
溢出并且结果被转换回unsigned short int
类型,则它再次被模2^N
减少,这将看起来好像值已经被包围。
想象一下,你的数据类型只有3位宽。 这允许您表示从0到7的8个不同值。如果添加1到7,则将“回绕”回到0,因为您没有足够的位来表示值8(1000)。
对于无符号类型,此行为已明确定义。 它没有为签名类型定义良好,因为有多种方法可以表示有符号值,并且溢出的结果将根据该方法进行不同的解释。
符号幅度:最高位表示符号; 0表示正数,1表示负数。 如果我的类型再次是三位宽,那么我可以表示如下的有符号值:
000 = 0 001 = 1 010 = 2 011 = 3 100 = -0 101 = -1 110 = -2 111 = -3
由于符号占用一位,因此我只有两位来编码0到3的值。如果我加1到3,我将溢出-0作为结果。 是的,有两个表示0,一个正面和一个负面。 您不会经常遇到符号幅度表示。
一个补码:负值是正值的按位反转。 再次,使用三位类型:
000 = 0 001 = 1 010 = 2 011 = 3 100 = -3 101 = -2 110 = -1 111 = -0
我有三位来编码我的值,但范围是[-3,3]。 如果我加1到3,我将溢出-3作为结果。 这与上面的符号幅度结果不同。 同样,使用此方法有两种编码为0。
二进制补码:负值是正值的按位反转加1.在三位系统中:
000 = 0 001 = 1 010 = 2 011 = 3 100 = -4 101 = -3 110 = -2 111 = -1
如果我加1到3,那么我将溢出-4,这与前两种方法不同。 请注意,我们有一个稍大的值范围[-4,3],只有一个表示为0。
二进制补码可能是表示有符号值的最常用方法,但它不是唯一的,因此C标准无法保证溢出有符号整数类型时会发生什么。 因此它保留了未定义的行为,因此编译器不必处理解释多个表示。
当有符号整数类型可以表示为符号和幅度,一个补码或两个补码时, 未定义的行为来自早期的可移植性问题。
如今,所有体系结构都将整数表示为两个补码。 但要小心:由于您的编译器认为您不会运行未定义的行为是正确的,因此在启用优化时可能会遇到奇怪的错误。
在带符号的8位整数中,环绕的直观定义可能看起来像是从+127到-128 – 在二进制补码二进制中:0111111(127)和1000000(-128)。 如您所见,这是递增二进制数据的自然进展 – 不考虑它表示整数,有符号或无符号。 直观地说,当在无符号整数的环绕感中从-1(11111111)移动到0(00000000)时发生实际溢出。
这没有回答更深层次的问题,即当有符号整数溢出时正确的行为是什么,因为根据标准没有“正确”的行为。