乍一看,这个问题可能看起来像是 如何检测整数溢出?的复制品,但实际上它是明显不同的。
我发现,虽然检测无符号整数溢出非常简单,但是在 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)不会给编译器优化溢出检查的机会?我在想也许有办法可以把两个操作数都转换为无符号的,并且通过滚动自己的二补运算来执行检查,但我真的不知道如何做到这一点。