5

我在 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

4 回答 4

5

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

于 2012-07-12T20:43:12.350 回答
5

在您的代码中,加法不会溢出,除非intshort. 由于默认提升,对和提升到x+y的值执行,然后以实现定义的方式将结果截断为。xyintshort

为什么不简单地做:return x+y<=SHRT_MAX && x+y>=SHRT_MIN;

于 2012-07-12T21:03:24.453 回答
3

在 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 (最低)

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

char 类型的环绕点


更新:为什么int不能反对charandshort的原因是因为当两个数字相加时,有可能溢出(不管是int,shortchar, 同时不要忘记积分提升),但是因为"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;
}

检查这里这里

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

参考:此处为C99 6.3.1.3 ,此处为 GNU C 手册。

于 2012-07-13T04:58:29.310 回答
1

编译器可能只是用 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

对于相反的情况也是如此。

于 2012-07-12T20:47:27.047 回答