乍一看,这个问题似乎与“如何检测整数溢出”重复出现。,但实际上有很大的不同。
我发现,虽然检测无符号整数溢出非常简单,但是在C / C ++中检测带符号溢出实际上比大多数人想象的要困难。
最明显但最幼稚的方式是:
int add(int lhs, int rhs)
{
int sum = lhs + rhs;
if ((lhs >= 0 && sum < rhs) || (lhs < 0 && sum > rhs)) {
/* an overflow has occurred */
abort();
}
return sum;
}
这样做的问题是根据C标准,有符号整数溢出是未定义的行为。 换句话说,根据标准,甚至在导致签名溢出时,程序就如同取消引用空指针一样无效。因此,您不能导致未定义的行为,然后尝试在事后检测溢出,如上述后置条件检查示例中所示。
即使上面的检查可能在许多编译器上都有效,但您不能指望它。实际上,由于C标准说未定义有符号整数溢出,因此某些编译器(如GCC)会在设置优化标志时优化上述检查,因为编译器认为有符号溢出是不可能的。这完全中断了检查溢出的尝试。
因此,另一种检查溢出的可能方法是:
int add(int lhs, int rhs)
{
if (lhs >= 0 && rhs >= 0) {
if (INT_MAX - lhs <= rhs) {
/* overflow has occurred */
abort();
}
}
else if (lhs < 0 && rhs < 0) {
if (lhs <= INT_MIN - rhs) {
/* overflow has occurred */
abort();
}
}
return lhs + rhs;
}
这似乎更有希望,因为在事先确保执行此类加法操作不会导致溢出之前,我们实际上不会将两个整数相加。因此,我们不会引起任何未定义的行为。
但是,不幸的是,该解决方案的效率比初始解决方案低很多,因为您必须执行减法运算只是为了测试加法运算是否有效。即使您不关心这种(小的)性能下降,我仍然不完全相信此解决方案是足够的。该表达式lhs <= INT_MIN - rhs
似乎完全类似于编译器可能会优化掉的那种表达式,认为有符号溢出是不可能的。
那么,这里有更好的解决方案吗?是否可以保证1)不会导致未定义的行为,以及2)不能为编译器提供优化溢出检查的机会?我当时想通过将两个操作数都转换为无符号,然后通过滚动自己的二进制补码算术执行检查可能有某种方法,但是我不确定如何做到这一点。