尽管您的问题已经很老了,但由于您尚未更改代码的那部分,我认为问题仍然存在,并且您仍然没有得到关于这里实际发生的事情的有用解释,所以让我尝试一下:
在 C(和 C++)中,如果添加两个有符号整数导致溢出,则整个程序运行的行为是未定义的。因此,执行您的程序的环境可以为所欲为(格式化您的硬盘或开始一场核战争,假设存在必要的硬件)。
gcc 通常两者都不做,但它会做其他令人讨厌的事情(在不幸的情况下仍可能导致其中任何一个)。为了证明这一点,让我给你一个来自 Felix von Leitner @ http://ptrace.fefe.de/int.c的简单例子:
#include <assert.h>
#include <stdio.h>
int foo(int a) {
assert(a+100 > a);
printf("%d %d\n",a+100,a);
return a;
}
int main() {
foo(100);
foo(0x7fffffff);
}
注意:我添加了 stdio.h,以消除有关未声明 printf 的警告。
现在,如果我们运行它,我们希望代码在第二次调用 foo 时断言,因为它会创建一个整数溢出并检查它。所以,让我们这样做:
$ gcc -O3 int.c -o int && ./int
200 100
-2147483549 2147483647
怎么回事?(WTF,德语为“Was täte Fefe”-“Fefe 会做什么”,Fefe 是 Felix von Leitner 的昵称,我借用了代码示例)。哦,顺便说一句,是 täte Fefe 吗?正确:在 2007 年就这个问题写一个错误报告!https://gcc.gnu.org/bugzilla/show_bug.cgi?id=30475
回到你的问题。如果您现在尝试深入挖掘,您可以创建一个程序集输出 (-S) 并进行调查以发现assert
已完全删除
$ gcc -S -O3 int.c -o int.s && cat int.s
[...]
foo:
pushq %rbx // save "callee-save" register %rbx
leal 100(%rdi), %edx // calc a+100 -> %rdx for printf
leaq .LC0(%rip), %rsi // "%d %d\n" for printf
movl %edi, %ebx // save `a` to %rbx
movl %edi, %ecx // move `a` to %rcx for printf
xorl %eax, %eax // more prep for printf
movl $1, %edi // and even more prep
call __printf_chk@PLT // printf call
movl %ebx, %eax // restore `a` to %rax as return value
popq %rbx // recover "callee-save" %rbx
ret // and return
在任何地方都没有断言。
现在,让我们在编译期间打开警告。
$ gcc -Wall -O3 int.c -o int.s
int.c: In function 'foo':
int.c:5:2: warning: assuming signed overflow does not occur when assuming that (X + c) >= X is always true [-Wstrict-overflow]
assert(a+100 > a);
^~~~~~
所以,这条消息实际上说的是:a + 100
可能会溢出导致未定义的行为。因为你是一个技术娴熟的专业软件开发人员,从不做错任何事,我(gcc)肯定知道,这a + 100
不会溢出。因为我知道,我也知道,这a + 100 > a
总是正确的。因为我知道,我知道断言永远不会触发。因为我知道,我可以在“死代码消除”优化中消除整个断言。
这正是 gcc 在这里所做的(并警告您)。
现在,在您的小示例中,数据流分析可以确定该整数实际上没有溢出。所以,gcc 不需要假设它永远不会溢出,相反,gcc 可以证明它永远不会溢出。在这种情况下,删除代码是绝对可以的(编译器仍然可以在这里警告删除死代码,但是 dce 经常发生,可能没有人想要这些警告)。但是在你的“真实世界代码”中,数据流分析失败了,因为并非所有必要的信息都存在。所以,gcc 不知道是否a++
溢出。所以它警告你,它假设永远不会发生(然后 gcc 删除整个 if 语句)。
解决(或隐藏!!!)这里问题的一种方法是a < INT_MAX
在执行a++
. 无论如何,我担心你可能有一些真正的错误,但我必须进行更多调查才能弄清楚。但是,您可以自己弄清楚,以正确的方式创建您的 MVCE:获取带有警告的源代码,从包含文件中添加任何必要的内容以获取独立的源代码(gcc -E
有点极端,但可以完成工作),然后开始删除任何不会使警告消失的内容,直到您有一个无法再删除任何内容的代码。