乍一看,这个问题似乎与如何检测整数溢出?,但实际上有很大不同。
我发现虽然检测无符号整数溢出非常简单,但检测 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) 不会为编译器提供优化溢出检查的机会?我在想可能有一些方法可以通过将两个操作数都转换为无符号数,并通过滚动你自己的二进制补码算术来执行检查,但我不确定如何做到这一点。