检测C / C ++中的带符号溢出
乍一看,这个问题看起来像是如何检测整数溢出的重复?然而,它实际上是显着不同的。
我发现虽然检测无符号整数溢出非常简单,但在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)不为编译器提供优化溢出检查的机会?我想可能有一些方法可以通过将两个操作数转换为无符号来执行它,并通过滚动自己的二进制补码算法来执行检查,但我不确定如何做到这一点。
蝴蝶刀刀