【问题标题】:Does anyone know why gcc 4.8.4 optimizes this code in a infinite loop?有谁知道为什么 gcc 4.8.4 在无限循环中优化此代码?
【发布时间】:2015-09-28 14:47:25
【问题描述】:

我发现以下未经优化和-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(正如我在 PC 上所期望的那样)

加上-Os的代码结果:

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

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

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

【问题讨论】:

  • 不,不是。检查未定义的行为是没有用的。你的for 循环最终会溢出一个有符号整数 -> UB!
  • 该行为仅针对 unsigned 定义
  • @SergioFormiggini:“但我想要!”。 我的想法是,如果你不能接受 C 是什么,你应该坚持使用 Python 或其他东西。
  • 未定义的行为意味着任何事情都可能发生。绝对任何事情,包括每次产生不同的结果。或者根本没有结果。
  • gcc 有选项 -ftrapv 使签名溢出崩溃和 -fwrapv 使其环绕。

标签: c gcc assembly


【解决方案1】:

编译器正在正常工作。

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


编译器将for(i=3;i&gt;2;i++);替换为while(1);,因为它看到i从3开始并且只会增加,所以值永远不会小于3。

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

由于存在无限循环,printf 永远无法到达,可以删除。


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

【讨论】:

  • 未优化的版本有效,因为 C int 行为与 CPU 相同 ...
  • @SergioFormiggini 是的,那是个意外。
  • @Sergio - 它仍然未定义,但 许多 可能的结果之一是显示您期望的值。这也是允许的。
  • 想问问Kernighan和Ritchie的意见!
  • @SergioFormiggini:C99 和 K&R C 现在是完全不同的实现。许多当前代码会被 K&R 编译器拒绝,而且很多 K&R 代码也会被符合 C99 的编译器拒绝!我在回答中引用了当前标准(草案),解释了为什么您调用未定义的行为并因此得到未定义的结果。我同意你的观点,并且非常不喜欢这种编译器行为,但它符合标准的。
【解决方案2】:

好吧,编译器可以假设程序永远不会表现出未定义的行为。

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

所以编译器可以说:

  • 循环以大于限制的索引值开始
  • 索引总是增加
  • 如果没有出现 UB,index 将永远大于限制 => 这是一个无限循环

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

你调用了未定义的行为并得到了……未定义的行为。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 2015-12-21
    相关资源
    最近更新 更多