乍一看,这个问题可能看起来像是How to detect integer overflow?的翻版,但实际上有很大不同.

我发现,虽然检测无符号整数溢出相当简单,但在C/C++中检测signed溢出实际上比大多数人想象的要困难得多.

最明显但最天真的方法是:

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标准,有符号整数溢出是undefined behavior.,换句话说,根据该标准,只要你引起一个有符号溢出,你的程序就像你取消引用一个空指针一样无效.因此,您不能导致未定义的行为,然后try 在事后检测溢出,如上面的后置条件判断示例所示.

尽管上面的判断可能会在许多编译器上运行,但您不能指望它.事实上,因为C标准说带符号的整数溢出是未定义的,所以一些编译器(如GCC)在设置优化标志时会出现optimize away the above check%,因为编译器认为带符号的溢出是不可能的.这完全打破了判断溢出的try .

因此,判断溢出的另一种可能方法是:

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)不会为编译器提供优化溢出判断的机会?我在想可能有办法做到这一点,将两个操作数都强制转换为无符号,并通过滚动自己的二补运算来执行判断,但我真的不确定如何做到这一点.

推荐答案

你的减法方法是正确的,定义得很好.编译器无法将其优化.

另一个正确的方法是,如果有一个更大的整数类型可用,则在较大的类型中执行算术运算,然后在将其转换回较小的类型时判断结果是否适合

int sum(int a, int b)
{
    long long c;
    assert(LLONG_MAX>INT_MAX);
    c = (long long)a + b;
    if (c < INT_MIN || c > INT_MAX) abort();
    return c;
}

一个好的编译器应该将整个加法和if语句转换为int大小的加法和溢出时的单个条件跳转,并且永远不会执行更大的加法.

Edit:正如Stephen所指出的,我很难找到(不太好的)编译器gcc来生成理智的asm.它生成的代码并不是非常慢,但肯定是次优的.如果有人知道这段代码的变体可以让gcc做正确的事情,我很乐意看到它们.

C++相关问答推荐

将 typewriter LF打印到Windows终端,而不是隐含的CR+LF

堆栈帧和值指针

为什么I2C会发送错误的数据?

ESP32在vTaskDelay上崩溃

为什么STM32G474RE上没有启用RCC PLL

Rust FFI--如何用给出返回引用的迭代器包装C风格的迭代器?

Setenv在c编程中的用法?

为什么我会收到释放后堆使用错误?

有什么方法可以将字符串与我们 Select 的子字符串分开吗?喜欢:SIN(LOG(10))

这个计算C中阶乘的函数正确吗?

C指针概念分段故障

C I/O:在Windows控制台上处理键盘输入

指向不同类型的指针是否与公共初始序列规则匹配?

C:如何将此代码转换为与数组一起使用?

C语言中奇怪的输出打印数组

如何修复我的qsort()算法?它每次都给出不同的结果

宏观;S C调深度

哪个首选包含第三个库S头文件?#INCLUDE;文件名或#INCLUDE<;文件名&>?

C:面筋蛋白';为什么不刷新窗口?

在 C 中的 scanf() 格式说明符中使用宏获取字符串长度