【问题标题】:Why assembly code is different for simple C program with different gcc version?为什么具有不同 gcc 版本的简单 C 程序的汇编代码不同?
【发布时间】:2017-03-02 19:41:12
【问题描述】:

我了解汇编和 c 编程的基础知识。

我用 C 编译了以下简单程序,

#include <stdio.h>

int main()
{
  int a;
  int b;
  a = 10;
  b = 88

  return 0;
}

使用以下命令编译,

gcc -ggdb -fno-stack-protector test.c -o test

上述程序gcc 4.4.7版本的反汇编代码为:

5                      push   %ebp
89 e5                   mov    %esp,%ebp
83 ec 10                sub    $0x10,%esp
c7 45 f8 0a 00 00 00    movl   $0xa,-0x8(%ebp)
c7 45 fc 58 00 00 00    movl   $0x58,-0x4(%ebp)
b8 00 00 00 00          mov    $0x0,%eax
c9                      leave
c3                      ret
90                      nop

但是,使用 gcc 版本 4.3.3 的同一程序的反汇编代码是:

8d 4c 23 04     lea     0x4(%esp), %ecx
83 e4 f0        and     $0xfffffff0, %esp
55              push    -0x4(%ecx)
89 e5           mov     %esp,%ebp
51              push     %ecx
83 ec 10        sub      $0x10,%esp
c7 45 f4 0a 00 00 00 00 movl $0xa, -0xc(%ebp)
c7 45 f8 58 00 00 00 00 movl $0x58, -0x8(%ebp)
b8 00 00 00 00          mov $0x0, %eax
83 c4 10                add $0x10,%esp
59                      pop %ecx
5d                      pop %ebp
8d 61 fc                lea -0x4(%ecx),%esp
c3                      ret 

为什么汇编代码有差异?
正如您在第二个汇编代码中所见,为什么将 %ecx 推入堆栈?
and $0xfffffff0, %esp有什么意义?

注意:操作系统相同

【问题讨论】:

  • 为什么不应该有区别?
  • 编译器会随着时间而改变和改进。所以你希望 4.4.7 生成比 4.3.3 更好的代码。
  • @ForceBru ups...“程序集应该是从 C 语言到机器语言的直接翻译”——绝对不是,对编译器的任何要求都比不上它。如果你想要直接指令 -> 机器码翻译,你应该使用汇编。
  • @jforberg:我们甚至不知道操作系统和 gcc 的默认优化设置(不,命令行不够)。 OP 没有研究 C 翻译器必须做什么(即抽象机器等)。已经有很多材料可以通过简单的搜索找到。
  • @ForceBru 很好,他们在 C 中做同样的事情,但是 C 没有直接翻译成机器代码,有几十到几百种如何翻译 C 源代码的合理方法,数百万不合理的方法。即使您使用-O3 告诉编译器“我想要最快的代码”,这也是 NP 完全问题,编译器无法解决,并且输出只是启发式地接近最优解。您逻辑中的主要问题是“您只能以一种方式有效地做到这一点”-您不能。即使是中型任务的汇编程序员也会产生不同的代码,有太多 [***llions] 方法。

标签: c gcc assembly


【解决方案1】:

为了让您了解一下,有多少种方法可以为特定任务创建有效代码,我认为这个示例可能会有所帮助。

有时会有大小编码比赛,显然是针对汇编程序员的,因为在这个级别上你根本无法与编译器竞争手写汇编。

比赛任务相当琐碎,以使入门级别和总努力合理,具有精确的输入和输出规范(精确到单字节或像素完美)。

所以你有几乎微不足道的精确任务,人工生成的代码(目前在微不足道的任务中仍然优于编译器),以单一的简单规则“最小尺寸”作为目标。

按照您的逻辑,绝对清楚每个竞争对手都应该产生相同的结果。

现实世界对此的回答是:

Hugi Size Coding Competition Series - Compo29 - 随机迷宫生成器

12 个条目,代码大小(以字节为单位):122、122、128、135、136、137、147、... 278 (!)。

我敢打赌,前两个条目都具有 122B,可能已经足够不同了(懒得实际检查它们)。

现在从高级编程语言和机器(编译器)生成有效的机器代码是更复杂的任务。并且编译器在推理方面无法与人类竞争,大多数“c++ 编译器生成的代码有多好”源于 C++ 语言本身的定义非常接近机器代码(易于编译)以及允许编译器的蛮力 CPU 能力针对特定代码路径处理数千个变体,主要通过蛮力搜索接近最优的解决方案。

优化器背后的数值“推理”仍然以他们自己的方式是最先进的,达到了人类仍然无法到达的地步,但更像是他们自己的方式,就像人类无法达到编译器的效率一样在合理的范围内编译完整的应用程序。

在这一点上,一些调试代码在一些辅助序言/结尾指令中有所不同......即使您会发现优化代码的差异,并且差异对人类来说是“显而易见的”,编译器仍然是一项壮举至少可以产生这样的结果,因为编译器必须对特定代码应用通用规则,而无需真正了解任务的上下文。

【讨论】:

    【解决方案2】:

    编译器不需要为相同的源代码生成相同的汇编代码。只要 可观察的行为 相同,C 标准就允许编译器按照他们认为合适的方式优化代码。因此,不同的编译器可能会生成不同的汇编代码。

    对于您的代码,GCC 6.2-O3 仅生成:

    xor     eax, eax
    ret
    

    因为您的代码基本上什么都不做。因此,它被简化为一个简单的 return 语句。

    【讨论】:

    • 虽然你是对的,但我认为问题是编译器为什么会生成这样的代码。由于 OP 可以读取汇编代码,我想我们可以假设他也知道编译器有时会为同一个输入程序生成不同的代码。 (注意:我没有给你投反对票)
    • @jforberg“我认为问题是编译器为什么会生成这样的代码”——我的回答是因为它可以/被允许。我怀疑 OP 知道你声称 OP 知道什么,因为如果 OP 已经知道这些知识,就不会提出这个问题。
    • 我认为我们应该始终让 OP 从怀疑中受益,特别是因为关于 SO 的答案不仅仅是为了 OP 的利益。我个人不知道为什么 GCC 会在第二个示例中生成代码,但我很想知道。这个线程中没有人甚至试图回答这个问题,我认为这很可悲。我现在就停下来,因为我不想在同一天和你吵架两次。晚上好。
    • @jforberg 你可以回答。它已经在 cmets 中得到了回答,它可能是一个调试版本,但我们没有得到足够的关于 gcc 是如何构建的信息。但是,如果 OP 澄清了这个问题,那就太好了。
    • @jforberg 我没有和你或任何人打架。我在 that 上发表了一条评论,这也是针对Martijn Pieters 的(他对此做出了回应)。如果不同意你就叫打架,那我只能说欢迎上网!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-01
    • 2016-11-28
    • 1970-01-01
    • 1970-01-01
    • 2021-09-16
    • 2013-02-19
    相关资源
    最近更新 更多