我在C规范中读到一点,无符号变量(特别是 unsigned short int
)在整数溢出时执行一些所谓的环绕,尽管我除了我用未定义的行为离开之外,找不到任何有关已签名变量的内容。
我的教授告诉我他们的价值观也被包围了(也许他只是意味着gcc)。我认为这些位被截断了,我留下的位给了我一些奇怪的值!
什么是环绕的,它与截断位有什么不同。
答案 0 :(得分:19)
有符号整数变量在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
缩小,这看起来好像值已经被包围了。
答案 1 :(得分:10)
想象一下,你的数据类型只有3位宽。这允许您表示8个不同的值,从0到7.如果添加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标准无法保证溢出有符号整数类型时会发生什么。所以它留下行为 undefined ,因此编译器不必处理解释多个表示。
答案 2 :(得分:3)
未定义的行为来自早期的可移植性问题,当有符号整数类型可以表示为sign&amp;幅度,一个补码或两个补码。
如今,所有架构都将整数表示为两个补码。但要小心:因为你的编译器认为你不会运行未定义的行为是正确的,所以当你进行优化时你可能会遇到奇怪的错误。
答案 3 :(得分:3)
在带符号的8位整数中,环绕的直观定义可能看起来像是从+127到-128 - 在二进制补码二进制中:0111111(127)和1000000(-128)。如您所见,这是递增二进制数据的自然进展 - 不考虑它表示整数,有符号或无符号。与直觉相反,当在无符号整数的环绕感中从-1(11111111)移动到0(00000000)时发生实际溢出。
这并没有回答更深层次的问题,即当有符号整数溢出时,正确的行为是什么,因为根据标准没有“正确”的行为。