我在C规范中读到,无符号变量(特别是unsigned short int
)在整数溢出时会执行所谓的回绕(wrap around)操作,但除了我发现有未定义行为(undefined behavior)之外,我找不到任何关于有符号变量的内容。
我的教授告诉我,它们的值也会被回绕(也许他只是指gcc)。我原以为位只是被截断了,留下的位给了我一些奇怪的值!
什么是回绕操作,它与截断位有何不同。
我在C规范中读到,无符号变量(特别是unsigned short int
)在整数溢出时会执行所谓的回绕(wrap around)操作,但除了我发现有未定义行为(undefined behavior)之外,我找不到任何关于有符号变量的内容。
我的教授告诉我,它们的值也会被回绕(也许他只是指gcc)。我原以为位只是被截断了,留下的位给了我一些奇怪的值!
什么是回绕操作,它与截断位有何不同。
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
进行缩减,这会使得值看起来像是被包裹了一样。
unsigned i
表示 unsigned int i
,就像 long i
表示 long int i
一样。在这种情况下,默认为 int
。至于您的例子,y*=z
被解释为 y = (short) ((int) y * (int) z)
,这可以简化为 y = (short) 261632
。请注意,在此情况下,在算术运算期间没有溢出(它们完全适合 int
),但在转换回 short
时会发生溢出。 在这种情况下的行为取决于实现。 - AnT stands with Russiai+1 > i
视为无条件真,但不允许编译器否定因果关系。超越这一点带来了多少优势? - supercat假设你有一个只有3位的数据类型。这使你能够表示8个不同的值,从0到7。如果你将7加1,则会“回绕”回到0,因为你没有足够的位来表示值8(1000)。
对于无符号类型,这种行为是明确定义的。但是对于有符号类型,这种行为是未定义的,因为有多种表示有符号值的方法,并且溢出的结果将根据该方法进行不同的解释。
表示-幅度法:最高位表示符号;0表示正数,1表示负数。如果我的类型再次为三位,则我可以表示如下的有符号值:
000 = 0
001 = 1
010 = 2
011 = 3
100 = -0
101 = -1
110 = -2
111 = -3
由于一位用于表示符号,因此我只有两位来编码0到3的值。如果我将3加1,则会溢出并得到-0作为结果。是的,0有两种表示方式,一种是正数,一种是负数。你不太可能经常遇到符号数表示法。
反码:负值是正值的按位取反。同样,使用三位类型:
000 = 0
001 = 1
010 = 2
011 = 3
100 = -3
101 = -2
110 = -1
111 = -0
我有三个比特位来编码我的值,但范围是[-3, 3]。如果我将3加1,则会溢出并得到结果-3。这与上面的带符号大小结果不同。同样,使用此方法,存在两种编码方式可表示0。
二进制补码:负数的二进制补码是正数按位取反再加1。在三位系统中:
000 = 0
001 = 1
010 = 2
011 = 3
100 = -4
101 = -3
110 = -2
111 = -1
如果我将3加1,结果会溢出为-4,这与先前的两种方法不同。请注意,我们的值范围略大[-4, 3],而且0只有一种表示方法。
补码可能是最常用的表示有符号数的方法,但它并不是唯一的方法,因此C标准不能保证当您溢出有符号整数类型时会发生什么。因此,它将行为定义为未定义,以便编译器不必处理多个表示的解释。
未定义行为源于早期的可移植性问题,因为有符号整数类型可以表示为符号和大小、反码或二进制补码。
现在,所有架构都将整数表示为二进制补码,会出现回绕现象。但要小心:由于编译器假定您不会运行未定义行为,所以在优化开启时可能会遇到奇怪的错误。
-fSomething
开关时,结果将被包装成二进制补码。”或者,如果GCC没有说明这一点,那么Fred Doe可以查看GCC源代码,根据需要进行修改,并发布一个以特定方式运行的新版本GCC - Eric Postpischil