C中的二进制补码溢出

时间:2012-07-12 20:39:55

标签: c debugging

我在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进行检查)
你能看到这里有什么问题吗?

4 个答案:

答案 0 :(得分:5)

因为在2s补码中,整数可以排列成一个圆(在modulo arithmetic意义上)。添加y然后减去y总会让你回到你开始的地方(尽管有未定义的行为)。

答案 1 :(得分:5)

在您的代码中,添加不会溢出,除非intshort的大小相同。由于默认促销活动,对x+yx提升为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(最低)

如果值非常大,它会一直环绕,直到达到可以在其范围内表示的值。

wrap-around point for char type


更新: intcharshort无效的原因是因为当两个数字相加时,有可能溢出(无论是intshort还是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;
}

检查herehere

您需要提供您正在处理的体系结构,以及您测试的实验值是什么,因为并非每个人都面对您所说的内容,并且因为您的问题的实现定义性质。

参考:C99 6.3.1.3 here和GNU C手册here

答案 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

,反之亦然。