我编写了一个非常简单的程序来测试gcc -O2选项,它会产生错误的行为。这是gcc的错误吗?
我的C程序:
#include <stdio.h>
int main(int argc, char **argv)
{
unsigned long i = 0;
while (1) {
if (++i > 0x1fffffffUL) {
printf("hello\n");
i = 0;
}
}
}
当我用-O1编译它时,一切都是正确的,我在每个循环中加1。但是当我用-O2编译它时,我被忽略了,&#34;你好&#34;在每个循环中输出。为什么-O2会导致这种错误行为?是gcc的明显错误吗?我的gcc版本对于cygwin是4.8.3,我尝试了一个新版本,它也有同样的问题。
使用-O1编译,汇编代码为:
Disassembly of section .text:
00000000 <_main>:
#include <stdio.h>
int main(int argc, char **argv)
{
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 e4 f0 and $0xfffffff0,%esp
6: 83 ec 10 sub $0x10,%esp
9: e8 00 00 00 00 call e <_main+0xe>
e: b8 00 00 00 20 mov $0x20000000,%eax
unsigned long i = 0;
while (1) {
if (++i > 0x1fffffffUL) {
13: 83 e8 01 sub $0x1,%eax
16: 75 fb jne 13 <_main+0x13>
printf("hello\n");
18: c7 04 24 00 00 00 00 movl $0x0,(%esp)
1f: e8 00 00 00 00 call 24 <_main+0x24>
24: eb e8 jmp e <_main+0xe>
26: 90 nop
27: 90 nop
使用-O2编译,汇编代码为:
Disassembly of section .text.startup:
00000000 <_main>:
#include <stdio.h>
int main(int argc, char **argv)
{
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 e4 f0 and $0xfffffff0,%esp
6: 83 ec 10 sub $0x10,%esp
9: e8 00 00 00 00 call e <_main+0xe>
e: 66 90 xchg %ax,%ax
10: c7 04 24 00 00 00 00 movl $0x0,(%esp)
17: e8 00 00 00 00 call 1c <_main+0x1c>
1c: eb f2 jmp 10 <_main+0x10>
1e: 90 nop
1f: 90 nop
答案 0 :(得分:6)
此优化是正确的。
在-O1
,编译器基本上将您的函数重写为:
for (;;) {
for (int i = 0; i < 0x20000000; i++) {
}
printf("hello\n");
}
在-O2
,空循环正在优化。
答案 1 :(得分:5)
听起来不像是我的错误。为什么编译器生成的代码会在程序的可观察行为在无限循环中输出“hello \ n”时浪费时间。您不需要增加无用的变量。
答案 2 :(得分:2)
它注意到你在循环中没有做任何其他事情,因此将它们优化掉。你仍然得到相同的输出。这就是优化的全部要点:只要生成相同的输出,它就会消除内部无用的东西。
编辑:如this post中所述,如果要保持空循环不被优化,可以在while循环体内包含asm("");
,一个空的汇编命令。这使GCC无法优化它。
答案 3 :(得分:1)
您会期待什么行为?基本上i++ > 0x1FFFFFFFUL
条件将是无限次的真实,并且每次都是真的它将打印&#34;你好&#34;。
外部可观察的行为是打印&#34;你好&#34;无限次。它从编译器那里得到了相当过于聪明的优化,只是产生了它。
如果你想要一个空循环实际上是循环的,你可能不应该告诉编译器对它进行优化,或者更好地确保每次运行循环都有外部可观察的行为。根据定义,这可以通过volatile
声明i
:
#include <stdio.h>
int main(int argc, char **argv)
{
static volatile unsigned long i = 0;
while (1) {
if (++i > 0x1fffffffUL) {
printf("hello\n");
i = 0;
}
}
}
volatile
声明基本上意味着编译器必须生成实际执行对该变量的每次写入的代码。