有谁知道为什么gcc 4.8.4在无限循环中优化这段代码?

时间:2015-09-28 14:47:25

标签: c gcc assembly

我发现在没有优化和-Os优化的情况下编译的以下代码的汇编程序结果之间存在差异。

#include <stdio.h>

int main(){
    int i;

    for(i=3;i>2;i++);

    printf("%d\n",i);

    return 0;
}

没有优化代码结果:

000000000040052d <main>:
  40052d:   55                      push   %rbp
  40052e:   48 89 e5                mov    %rsp,%rbp
  400531:   48 83 ec 10             sub    $0x10,%rsp
  400535:   c7 45 fc 03 00 00 00    movl   $0x3,-0x4(%rbp)
  40053c:   c7 45 fc 03 00 00 00    movl   $0x3,-0x4(%rbp)
  400543:   eb 04                   jmp    400549 <main+0x1c>
  400545:   83 45 fc 01             addl   $0x1,-0x4(%rbp)
  400549:   83 7d fc 02             cmpl   $0x2,-0x4(%rbp)
  40054d:   7f f6                   jg     400545 <main+0x18>
  40054f:   8b 45 fc                mov    -0x4(%rbp),%eax
  400552:   89 c6                   mov    %eax,%esi
  400554:   bf f4 05 40 00          mov    $0x4005f4,%edi
  400559:   b8 00 00 00 00          mov    $0x0,%eax
  40055e:   e8 ad fe ff ff          callq  400410 <printf@plt>
  400563:   b8 00 00 00 00          mov    $0x0,%eax
  400568:   c9                      leaveq 
  400569:   c3                      retq  

,输出为:-2147483648(正如我在电脑上预期的那样)

使用-Os代码结果:

0000000000400400 <main>:
  400400:   eb fe                   jmp    400400 <main>

我认为第二个结果是错误!!!我认为编译器应该编译对应于代码的东西:

printf("%d\n",-2147483648);

2 个答案:

答案 0 :(得分:6)

编译器正在按预期工作。

有符号整数溢出在C中是非法的,并导致未定义的行为。任何依赖它的程序都会被破坏。

编译器将for(i=3;i>2;i++);替换为while(1);,因为它看到i从3开始并且仅增加,因此值永远不会小于3.

只有溢出才能导致循环退出。但这是非法的,编译器假定你永远不会做这么脏的事情。

因为存在无限循环,所以永远不会到达printf并且可以将其删除。

未经优化的版本只能偶然使用。编译器可以在那里做同样的事情,它本来也同样有效。

答案 1 :(得分:4)

好吧,允许编译器假定程序永远不会出现未定义的行为。

在第一种情况下你得到INT_MIN,因为当INT_MAX + 1给出INT_MIN(*)时你有溢出,但这是未定义的行为。并且C99草案(n1556)在6.5表达式中表示§5:如果在评估表达式期间发生异常情况(即,如果 结果不是数学定义的,或者不在其类型的可表示值范围内),行为未定义。

所以编译器可以说:

  • 循环以索引值大于限制
  • 开始
  • 索引总是增加
  • 如果没有UB发生,索引将始终大于limit =&gt;这是一个无限循环

使用as-if规则(5.1.2.3程序执行§3 An 实际实现不需要评估表达式的一部分,如果它可以推导出它 如果不使用值并且不产生所需的副作用),它可以用无限循环替换你的循环。无法再访问以下说明,可以将其删除。

您调用了未定义的行为并获得了......未定义的行为。

(*)甚至这显然依赖于实现,如果你有1的补码,则INT_MIN可以是-21474836478000000可以是负0,或者溢出可以引发信号...