为什么gcc在一种情况下警告"假设已签名溢出",而在另一种情况下则不然

dev*_*ull 2 c gcc integer-overflow compiler-warnings

请考虑以下代码:

$ 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;
}
Run Code Online (Sandbox Code Playgroud)

用它编译-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]
Run Code Online (Sandbox Code Playgroud)

现在考虑以下看起来在功能上等同于上面的代码:

$ 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;
}
Run Code Online (Sandbox Code Playgroud)

使用相同的选项进行编译不会导致任何警告.

$ gcc -O2 -Wall -Wextra -Wstrict-overflow p.c 
$
Run Code Online (Sandbox Code Playgroud)

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

我在Ubuntu上使用gcc 4.7.2:

$ gcc --version
gcc (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2
Run Code Online (Sandbox Code Playgroud)

我无法弄清楚这两种情况的区别.它是否与优化有关或者gcc在这里表现得不是很好吗?

Pas*_*uoq 7

这两个片段与编译器的优化程序内分析的观点不同.

在第一个中,GCC知道并告诉你,absolute()如果你认为有符号溢出总是产生二进制补码结果,它会将函数编译成可能与你预期不同的东西(它没有.有符号溢出是未定义的行为 .GCC可以选择它希望输入的任何行为absolute()导致签名溢出.在这种情况下,编译器将删除if (j<0) …为死代码,这是已定义输入的正确行为)

在第二个片段中,没有这种可能的误解,因为-i从不溢出(因为i可以看出1只有局部值分析).定义了函数的所有行为,没有理由在GCC和程序员之间产生分歧,也没有理由发出警告.

然后,事实是海湾合作委员会没有警告i=INT_MIN;.这可能是由应用传递的顺序引起的.我敢打赌,首先应用了一个常量传播传递,因此在应用j<0发出警告的复杂优化传递时已经计算了条件的值(*).后一个传递没有看到比较所以它没有理由警告.

如果你希望签名溢出总是产生两个补码结果,你可以使用gcc -fwrapv -fno-strict-overflow.Ian Lance Taylor在他的帖子中暗示了这些选项,但是他并没有说明为什么第二种选择对于真正让GCC表现出来的时候才有必要完成这项工作.使用这些选项应该使警告"假设没有发生签名溢出"消失.

(*)我应该坚持认为这是条件价值.同样,编译器可以选择它想要的任何值,因为计算-INT_MIN是此体系结构上的未定义行为.常数传播通道可以应用两个补码但不必.

  • `-fno-strict-overflow`和`-fwrapv`之间的区别主要是概念性的,据我所知:前者指示编译器不要假设溢出没有发生(编译器可能会发现``j <0`永远不会成立,但可能无法利用这一事实),后者重新定义算术运算,以便溢出是不可能的(编译器知道`j <0`是一种真正的可能性).如果你真的*希望*`-INT_MIN`有一个保证结果,我认为你应该使用`-fwrapv`(和`-f(no-)strict-overflow`在这种情况下被忽略),而不仅仅是`-fno -strict-overflow`. (2认同)