我在 C 中看到了一个错误代码,用于检查加法是否导致溢出。它适用于char
,但是当参数是时给出错误的答案int
,我不知道为什么。
这是带short
参数的代码。
short add_ok( short x, short y ){
short sum = x+y;
return (sum-x==y) && (sum-y==x);
}
这个版本工作正常,当你将参数更改为时出现问题int
(你可以检查它INT_MAX
)
你能看到这里有什么问题吗?
因为在 2s 补码中,整数可以排列成一个圆圈(在模算术的意义上)。添加 y 然后减去 y 总是会让你回到你开始的地方(尽管有未定义的行为)。
在您的代码中,加法不会溢出,除非int
与short
. 由于默认提升,对和提升到x+y
的值执行,然后以实现定义的方式将结果截断为。x
y
int
short
为什么不简单地做:return x+y<=SHRT_MAX && x+y>=SHRT_MIN;
在 C 编程语言中,有符号整数在转换为较小的有符号整数时,比如 char(为了简单起见),是实现定义的方式。尽管许多系统和程序员都假设回绕溢出,但这并不是一个标准。那么什么是环绕溢出呢?
二进制补码系统中的回绕溢出发生这样的情况,即当一个值不能再以当前类型呈现时,它会围绕可以呈现的最高或最低数字扭曲。那么这是什么意思?看一看。
在signed char中,可以呈现的最大值是127,最小值是-128。那么当我们执行“char i = 128”时会发生什么,存储在 i 中的值变为 -128。因为该值大于有符号整数类型,所以它围绕最小值,如果它是“char i = 129”,那么 i 将包含 -127。你能看见它吗?每当一端达到最大值时,它就会环绕另一端(符号)。反之亦然,如果“char i = -129”,那么 i 将包含 127,如果是“char i = -130”,它将包含 126,因为它达到了最大值并缠绕在最大值周围。
(最高) 127, 126, 125, ... , -126, -127, -128 (最低)
如果值非常大,它会不断回绕,直到达到可以在其范围内表示的值。
更新:为什么int
不能反对char
andshort
的原因是因为当两个数字相加时,有可能溢出(不管是int
,short
或char
, 同时不要忘记积分提升),但是因为"short"
andchar
的尺寸小于int
并且因为它们在表达式中被提升为int
,所以在这一行中再次表示它们而没有截断:
return (sum-x==y) && (sum-y==x);
所以任何溢出都会被检测到,后面会详细解释,但是当 with 时int
,它不会被提升为任何东西,所以会发生溢出。例如,如果我这样做INT_MAX+1
,那么结果是 INT_MIN,如果我通过 INT_MIN-1 == INT_MAX 测试溢出,结果是 TRUE!这是因为“short”和 char 被提升为 int、求值,然后被截断(溢出)。但是, int 首先溢出然后评估,因为它们没有提升到更大的大小。
想想没有提升的 char 类型,并尝试使用上图进行溢出并检查它们。您会发现添加或减去导致溢出的值会使您回到原来的位置。但是,这不是在 C 中发生的情况,因为 char 和“short”被提升为 int,因此检测到溢出,这在 int 中是不正确的,因为它被提升为更大的大小。
更新结束
对于您的问题,我在 MinGW 和 Ubuntu 12.04 中检查了您的代码,似乎工作正常。后来我发现代码实际上在short小于int并且值不超过int范围的系统中工作。这一行:
return (sum-x==y) && (sum-y==x);
是真的,因为 "sum-x" 和 "y" 被评估为 (int) 所以不会发生环绕,它发生在前一行(分配时):
short sum = x+y;
这是一个测试。如果我第一个输入 32767,第二个输入 2,那么当:
short sum = x+y;
由于环绕,总和将包含 -32767。但是,当:
return (sum-x==y) && (sum-y==x);
"sum-x" (-32767 - 32767) 只会在 wrap-round 发生时等于 y (2)(然后是 buggy),但由于积分提升,它永远不会以这种方式发生,并且 "sum-x" 值变为 - 65534 不等于 y,从而导致正确检测。
这是我使用的代码:
#include <stdio.h>
short add_ok( short x, short y ){
short sum = x+y;
return (sum-x==y) && (sum-y==x);
}
int main(void) {
short i, ii;
scanf("%hd %hd", &i, &ii);
getchar();
printf("%hd", add_ok(i, ii));
return 0;
}
您需要提供您正在处理的架构,以及您测试的实验值是什么,因为不是每个人都会面对您所说的内容,并且因为您的问题是由实现定义的性质。
编译器可能只是用 1 替换对该表达式的所有调用,因为它在每种情况下都是正确的。优化例程将对 sum 执行复制传播并得到
return (y==y) && (x==x);
进而:
return 1
在每种情况下都是如此,因为有符号整数溢出是未定义的行为——因此,编译器可以自由地保证 x+yy == x 和 y+xx == y。
如果这是一个无符号操作,它同样会失败——因为溢出只是作为模运算执行的,因此很容易证明
x+y mod SHRT_MAX - y mod SHRT_MAX == x
对于相反的情况也是如此。