【问题标题】:Why does gcc warn "assuming signed overflow" in one case and not in the other为什么 gcc 在一种情况下会警告“假设签名溢出”而不是在另一种情况下
【发布时间】:2013-07-22 10:40:04
【问题描述】:

考虑以下代码:

$ cat o.c 
#include <stdio.h>
#include <limits.h>

int absolute(int i) {
  int j = i < 0 ? -i : i;
  if (j<0)      /* This is line 6 */
    return 0;
  return j;
}

int main() {
  int i = 1;
  printf("%d %d\n", i, absolute(i));
  return 0;
}

使用-O2-Wstrict-overflow 编译会产生警告:

$ gcc -O2 -Wall -Wextra -Wstrict-overflow o.c 
o.c: In function ‘absolute’:
o.c:6:6: warning: assuming signed overflow does not occur when simplifying comparison of absolute value and zero [-Wstrict-overflow]

现在考虑以下在功能上与上述代码等效的代码:

$ cat p.c 
#include <stdio.h>
#include <limits.h>

int main() {
  int i = 1;
  int j = i < 0 ? -i : i;
  if (j<0) // Changing i to INT_MIN above and changing (j<0) to (j>INT_MAX)
           // doesn't change the behavior
    j=0;
  printf("%d %d\n", i, j);
  return 0;
}

使用相同的选项编译它不会导致任何警告。

$ gcc -O2 -Wall -Wextra -Wstrict-overflow p.c 
$

如第二个代码中所述,将赋值更改为 i=INT_MIN; 并将条件更改为 (j&gt;INT_MAX) 也不会发出警告。

我在 Ubuntu 上使用 gcc 4.7.2:

$ gcc --version
gcc (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2

我无法弄清楚这两种情况的区别。它与优化有关还是 gcc 在这里表现不佳?

【问题讨论】:

  • 我怀疑在第二种情况下,j 的值在 j&lt;0 得到优化时是已知的,但我不完全确定。

标签: c gcc compiler-warnings integer-overflow


【解决方案1】:

这两个sn-ps从编译器对优化的过程内分析的角度来看是不同的。

在第一个中,GCC 知道并告诉您,如果您认为有符号溢出总是产生二进制补码结果(它不会。@ 987654321@。GCC 可以为absolute() 的输入选择任何它希望导致有符号溢出的行为。在这种情况下,编译器将删除if (j&lt;0) … 作为死代码,这是定义输入的正确行为)

在第二个sn-p中,不存在这种可能的误解,因为-i永远不会溢出(因为i可以看出是1,只有局部值分析)。函数的所有行为都已定义,GCC 和程序员之间没有分歧的原因,也没有发出警告的原因。

然后,GCC 不会对i=INT_MIN; 发出警告。这可能是由应用通行证的顺序引起的。我敢打赌,首先应用了常量传播过程,因此在应用发出警告的复杂优化过程时,已经计算了条件 j&lt;0 的值(*)。后一遍看不到比较,因此没有理由发出警告。

如果您希望有符号溢出始终产生二进制补码结果,您可以使用gcc -fwrapv -fno-strict-overflow。 Ian Lance Taylor 在他的帖子中提到了这些选项,但他没有说明为什么在已经有一个应该完成这项工作的情况下,为什么需要第二个选项才能真正使 GCC 正常运行。使用这些选项应该会使“假设没有发生签名溢出”的警告消失。

(*) 我应该坚持认为这是条件的 a 值。同样,编译器可以选择它想要的任何值,因为计算 -INT_MIN 在此架构上是未定义的行为。常量传播过程可以应用二进制补码,但不是必须的。

【讨论】:

  • 感谢您的详细回复。似乎使用-fwrapv 就足够了。此外,文档说:When -fwrapv is used, there is no difference between -fstrict-overflow and -fno-strict-overflow for integers.
  • 除了您引用的 Ian Lance Taylor 的博客之外,来自 GCC 错误报告的this 评论还包含有关您提到的编译器标志的有用信息。
  • -fno-strict-overflow-fwrapv 之间的区别主要是概念性的,据我所知:前者指示编译器不要假设不会发生溢出(编译器可能会发现j &lt; 0 永远不会是真的,但可能不会利用这个事实),后者重新定义了算术运算,使得溢出是不可能的(编译器知道j &lt; 0 是一个真正的可能性)。如果你真的想要 -INT_MIN 有保证的结果,我认为你应该使用-fwrapv(在这种情况下-f(no-)strict-overflow 会被忽略),而不仅仅是-fno-strict-overflow
  • -fwrapv 指示编译器整数溢出换行。 -fno-strict-overflow 只是告诉编译器可能会发生整数溢出,而不是当它发生时会发生什么(基本上,插入/删除没有特殊代码)。如果你的处理器有饱和算术(所以 INT_MAX + 1 == INT_MAX),这可能就是会发生的情况。
  • @hvd:区别不仅仅是概念上的。给定-fno-strict-overflow 而不是-fwrapv,像int m=INT_MAX; long long L=m+1; 这样的代码可能将L 任意设置为INT_MAX+1L-INT_MAX-1 或其他一些值,但不会否定时间和因果律。没有-fno-strict-overflow,任何事情都可能发生。
【解决方案2】:

在第一个程序中,优化器识别绝对值的符号是用编译时未知的变量参数检查的。

在第二个程序中,i 的值是已知的,jj &lt; 0 的结果是在编译时计算的程序。

说服这一点的一个好方法是将第二个程序中 i 的值更改为仅在运行时才知道的值:

int i = 1 + printf(""); // 1 if no error, cannot be deduced
                        // at compile time
int j = i < 0 ? -i : i;
if (j<0) 

使用-O2 -Wstrict-overflow 编译时,会得到与第一个程序相同的警告。

【讨论】:

  • 谢谢,它解释了前一种情况下的警告。
猜你喜欢
  • 1970-01-01
  • 2020-02-03
  • 1970-01-01
  • 1970-01-01
  • 2011-11-01
  • 1970-01-01
  • 2013-12-28
  • 2020-03-31
  • 2021-12-30
相关资源
最近更新 更多