我在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
进行检查)
你能看到这里有什么问题吗?
答案 0 :(得分:5)
因为在2s补码中,整数可以排列成一个圆(在modulo arithmetic意义上)。添加y然后减去y总会让你回到你开始的地方(尽管有未定义的行为)。
答案 1 :(得分:5)
在您的代码中,添加不会溢出,除非int
与short
的大小相同。由于默认促销活动,对x+y
和x
提升为y
的值执行int
,然后在实施中将结果截断为short
- 定义的方式。
为什么不这样做:return x+y<=SHRT_MAX && x+y>=SHRT_MIN;
答案 2 :(得分:3)
在C编程语言中,转换为较小的有符号整数时,有符号整数,比如char(为简单起见),是实现定义的方式。尽管许多系统和程序员都假设环绕溢出,但它并不是标准。那么什么是环绕式溢出?
二进制补码系统中的环绕溢出发生时,当一个值不能再以当前类型呈现时,它会围绕可以呈现的最高或最低数字扭曲。那么这是什么意思?看一看。
在signed char中,可以显示的最高值是127,最低值是-128。那么当我们这样做时会发生什么:“char i = 128”,即存储在i中的值变为-128。因为该值大于有符号整数类型,所以它围绕最低值,如果它是“char i = 129”,那么我将包含-127。你能看见它吗?每当一个结束达到最大值时,它就绕着另一端(符号)缠绕。反之亦然,如果“char i = -129”,那么我将包含127,如果它是“char i = -130”,它将包含126,因为它达到了它的最大值并且包裹了最高值。
(最高)127,126,125,......, - 126,-127,-128(最低)
如果值非常大,它会一直环绕,直到达到可以在其范围内表示的值。
更新: int
与char
和short
无效的原因是因为当两个数字相加时,有可能溢出(无论是int
,short
还是char
,而不是忘记积分促销),但因为"short"
和char
的尺寸小于int
并且因为它们在表达式中被提升为int
,所以它们在此行中再次表示而不截断:
return (sum-x==y) && (sum-y==x);
因此,如后面详细解释的那样检测到任何溢出,但是当使用int
时,它不会被提升为任何内容,因此会发生溢出。例如,如果我执行INT_MAX+1
,则结果为INT_MIN,如果我通过INT_MIN-1 == INT_MAX测试溢出,则结果为TRUE! 这是因为“short”和char被提升为int,被评估,然后被截断(溢出)。但是,int首先溢出然后进行评估,因为它们不会被提升到更大的大小。
考虑没有促销的char类型,并尝试使用上面的插图进行溢出并检查它们。您会发现添加或减去导致溢出的值会使您返回到原来的位置。然而,这不是C中发生的事情,因为char和“short”被提升为int,因此检测到溢出,这在int中是不正确的,因为它被提升为更大的大小。
END OF UPDATE
对于您的问题,我在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;
sum将包含-32767,因为包装。但是,时间:
return (sum-x==y) && (sum-y==x);
“sum-x”(-32767 - 32767)只会等于y(2)(然后越野车)如果发生回绕,但由于整体提升,它永远不会发生这种情况并且“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;
}
您需要提供您正在处理的体系结构,以及您测试的实验值是什么,因为并非每个人都面对您所说的内容,并且因为您的问题的实现定义性质。
答案 3 :(得分:1)
编译器可能只是用1替换对该表达式的所有调用,因为它在每种情况下都是真的。优化例程将对sum执行复制传播并获取
return (y==y) && (x==x);
然后:
return 1
在每种情况下都是如此,因为有符号整数溢出是未定义的行为 - 因此,编译器可以自由地保证x + y-y == x和y + x-x == y。
如果这是一个无符号运算,它会同样失败 - 因为溢出只是作为模运算执行,所以很容易证明
x+y mod SHRT_MAX - y mod SHRT_MAX == x
,反之亦然。