我正在阅读有关检测C中溢出的技术。其中一个显示检测溢出的错误解决方案的例子就是这个:
/* Determine whether arguments can be added without overflow */
int tadd_ok(int x, int y) {
int sum = x+y;
return (sum-x == y) && (sum-y == x);
}
它说它不起作用,因为:
二次补充加法形成一个阿贝尔群,所以 表达式(x + y)-x将评估为y而不管是否 加法溢出,并且(x + y)-y将始终求值为x
它究竟意味着什么?这是否意味着C编译器用sum
替换x+y
?
为了弄清楚它是什么,我甚至追踪了程序的汇编代码,但没有替换的迹象。
更新:我的问题的实质是,GCC是否在不计算表达式的情况下评估表达式?
这是 NOT 关于两个补语的问题
您可以在here中看到示例输出。
答案 0 :(得分:2)
如果你采用4 (0b0100) + 5 (0b0101)
的一个简单例子,你可以看到无符号和应该是9 (1001)
,实际上是-7
的二进制补码。如果你那么取这个和(0b1001)并用二进制补码算法从中减去4:
0b1001 - 0b0100 = 0b1001 + 2s_complement(0b0100) = 0b1001 + 0b1100 = 0b1_0101
最终得到的是0101,这是5(在2的补码操作中,你丢失溢出最重要的1)。从总和等于4中减去5:
0b1001 - 0b0101 = 0b1001 + 2s_complement(0b0101) = 0b1001 + 0b1011 = 0b1_0100
这满足您提供的c代码,但仍然导致溢出。
来自维基百科关于two's complement的文章:
Two's complement Decimal 0111 7 0110 6 0101 5 0100 4 0011 3 0010 2 0001 1 0000 0 1111 −1 1110 −2 1101 −3 1100 −4 1011 −5 1010 −6 1001 −7 1000 −8
<强>更新强>
为了使用INT_MAX = 7的普通4位整数系统演示INT_MAX示例,我们可以看到与c代码相同的结果。
7 + 7 (0b0111 + 0b0111) = 0b1110 (-2 in two's complement)
就像我上面的示例一样,减去sum - 7
将等于7
。
0b1110 - 0b0111 = 0b1110 + 2s_complement(0b0111) = 0b1110 + 0b1001 = 0b1_0111
答案 1 :(得分:0)
我可能错了,但在我看来,这似乎是一个简单的溢出检查......如果总和溢出,sum-x不能等于y
更多:参数在堆栈中传递给函数,sum是一个局部变量,它也在堆栈中。编译器可能将sum = 0,sum + x和它们sum + y(堆栈位置+另一个堆栈位置)放入总和位置。对于return语句,它可能通过复制sum变量
将结果放入另一个临时位置答案 2 :(得分:0)
看起来导致错误结果的错误也会影响后续测试的两个方面,因此测试永远不会失败。
答案 3 :(得分:0)
如果你的编译器有优化(即-02),那么这个函数就不行了。但是,如果没有优化,我相信这会正常工作。
答案 4 :(得分:0)
对于您发布的代码,它将无法在所有平台上 ,因为某些平台将隐式地包含溢出。表达式将按以下方式进行评估:
int x = 0xFFFFFFF0;
int y = 0x00000020;
int sum = x + y; // value of sum is 0x00000010
// would be 0x100000010, but highest order bit is dropped
int diff = sum - y; // value of diff is 0xFFFFFFF0
// equal to x
域已关闭,即溢出将以可预测且可逆的方式环绕。您不能依赖此方法来检查溢出。
也就是说,这种行为是特定于平台的。有些处理器会这样做,其他处理器将返回最接近的可表示值(即sum将等于0xFFFFFFFF),在这种情况下,它将按预期工作。
检查C中的溢出的最佳方法是使用内联汇编语句将标志压入堆栈,将它们弹出到寄存器中,然后返回值。如果在最后一次数学运算中发生溢出,则将设置其中一个标志。我以前这样做过,但不记得怎么做了。有关资源和帮助,请参阅以下页面: