请考虑以下代码:
$ 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>INT_MAX)
也不会发出警告。
我在Ubuntu上使用gcc 4.7.2:
$ gcc --version
gcc (Ubuntu/Linaro 4.7.2-2ubuntu1) 4.7.2
我无法弄清楚这两种情况的区别。它与优化有关,还是gcc在这里表现不正常?
答案 0 :(得分:7)
这两个片段与编译器的优化程序内分析的观点不同。
在第一个中,GCC知道并告诉你,如果你认为签名溢出总是产生两个补码结果,它会将函数absolute()
编译成可能与你的预期不同的东西(它不会。Signed overflow is undefined behavior。GCC可以选择导致签名溢出的absolute()
输入所需的任何行为。在这种情况下,编译器将删除if (j<0) …
作为死代码,这是定义输入的正确行为)
在第二个片段中,没有这种可能的误解,因为-i
永远不会溢出(因为i
只能通过本地值分析看到1
。定义了函数的所有行为,GCC和程序员之间没有异议,也没有理由发出警告。
然后,事实是GCC没有警告i=INT_MIN;
。这可能是由应用传递的顺序引起的。我敢打赌,首先应用了一个常量传播过程,因此在应用发出警告的复杂优化过程时,已经计算了条件j<0
的值(*)。后一个传递没有看到比较,所以它没有理由警告。
如果您希望签名溢出始终产生两个补码结果,则可以使用gcc -fwrapv -fno-strict-overflow
。 Ian Lance Taylor在他的帖子中暗示了这些选项,但是他并没有说清楚为什么第二种选择对于真正让GCC表现出来的时候才有必要完成这项工作。使用这些选项应该使警告“假设没有发生签名溢出”消失。
(*)我应该坚持认为这是条件的 值。同样,编译器可以选择它想要的任何值,因为计算-INT_MIN
在此体系结构上是未定义的行为。常数传播通道可以应用二进制补码,但不必如此。
答案 1 :(得分:2)
在第一个程序中,优化器识别出绝对值的符号,并使用在编译时未知的变量参数进行检查。
在第二个程序中,i
的值已知,j
和j < 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
编译时,会在第一个程序中收到相同的警告。