【问题标题】:Can a compiler generate useless assembly code?编译器可以生成无用的汇编代码吗?
【发布时间】:2017-11-21 19:50:18
【问题描述】:

我正在尝试查找从 c 程序生成的汇编代码的含义。这是C语言的程序:

int* a = &argc;
int b = 8;
a = &b;

这是生成的带有解释的汇编代码。有一点不明白:

主要序幕:

leal    4(%esp), %ecx
andl    $-16, %esp
pushl   -4(%ecx)
pushl   %ebp
movl    %esp, %ebp
pushl   %ecx
subl    $36, %esp

在%eax中加载argc的地址:

movl    %ecx, %eax

我没有得到的部分:

movl    4(%eax), %edx
movl    %edx, -28(%ebp)

Stack-Smashing Protector 代码(设置):

movl    %gs:20, %ecx
movl    %ecx, -12(%ebp)
xorl    %ecx, %ecx

在 a 和 b 中加载值(参见 main.c):

movl    %eax, -16(%ebp)
movl    $8, -20(%ebp)

修改a的值(a = &b):

leal    -20(%ebp), %eax
movl    %eax, -16(%ebp)

Stack-Smashing Protector 代码(验证堆栈是否正常):

movl    $0, %eax
movl    -12(%ebp), %edx
xorl    %gs:20, %edx
je  .L7
call    __stack_chk_fail

如果堆栈正常:

.L7:
    addl    $36, %esp
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret

所以我不理解的部分是修改 -28(%ebp) 中的值,这是一个从未使用过的地址。有人知道为什么会生成这部分吗?

【问题讨论】:

  • 如果您在没有优化的情况下进行编译(默认),那么即使不需要,编译器输出也会包含所有加载/存储到内存中的内容。将您的代码放入不是 main 的函数中,并在 (-O3) 上使用优化进行编译
  • 不要使用 main(),让您的代码在测试中使用其他函数名称。 main() 问题已被问及回答了无数次......
  • 您告诉编译器通过不指定优化标志来关闭其大脑,现在您想知道它会生成奇怪的代码吗?
  • 如果我使用 -O1 进行优化,唯一剩下的就是 mov $0, %eax 然后 ret
  • @DJ_Joe 真该死。如果您想强制编译器发出无用的代码,请使用volatile。或者编写一些实际做一些有意义的工作的测试用例,编译器不能简单地删除。

标签: c gcc assembly x86


【解决方案1】:

查看编译器功能的好方法。我假设你有一个名为 main.c 的文件:

int main(int argc, char **argv) 
{
    int* a = &argc;
    int b = 8;
    a = &b;
}

使用调试信息编译到目标文件:

$ gcc -c -g main.c

查看程序集:

$ objdump -S main.o

main.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
int main(int argc, char **argv)
{
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   89 7d ec                mov    %edi,-0x14(%rbp)
   7:   48 89 75 e0             mov    %rsi,-0x20(%rbp)
    int* a = &argc;
   b:   48 8d 45 ec             lea    -0x14(%rbp),%rax
   f:   48 89 45 f8             mov    %rax,-0x8(%rbp)
    int b = 8;
  13:   c7 45 f4 08 00 00 00    movl   $0x8,-0xc(%rbp)
    a = &b;
  1a:   48 8d 45 f4             lea    -0xc(%rbp),%rax
  1e:   48 89 45 f8             mov    %rax,-0x8(%rbp)
  22:   b8 00 00 00 00          mov    $0x0,%eax
}
  27:   5d                      pop    %rbp
  28:   c3                      retq   

然后对完全优化做同样的事情:

$ gcc -c -g -O3 main.c 

然后再次查看程序集:

$ objdump -S main.o

main.o:     file format elf64-x86-64


Disassembly of section .text.startup:

0000000000000000 <main>:
int main(int argc, char **argv)
{
    int* a = &argc;
    int b = 8;
    a = &b;
}
   0:   31 c0                   xor    %eax,%eax
   2:   c3                      retq   

所以答案是肯定的。编译器可以生成不需要的指令。这就是您打开优化的原因。当它们被关闭时,编译器会以一种非常通用的方式完成其工作,而无需考虑。例如,它为未使用的变量保留空间。

【讨论】:

  • 好的,谢谢。我发现相当令人惊讶的是,汇编部分与 C 源代码的任何部分都不对应。但确实,既然程序什么都不做,那么程序集的每一行都应该被认为是无用的
  • 制作 C 函数以进行编译和反汇编以查看编译器在做什么是一门艺术。低优化级别编译器将所有内容保存到堆栈/内存并在那里检索表单,然后如果您有死代码,它可能会编写一些它从未读取过的预期内容,但这部分是因为您的程序可能已经这样做了。编写不是死代码或优化为不感兴趣的测试函数需要一些练习。
  • 可悲的是,即使在优化的代码中,OP 的标题问题的答案也是“是”,如果源代码足够复杂,编译器最终将不得不停止优化计算,并发出合理修剪正确的内容代码做它应该做的。创建最佳代码需要花费太多时间,对于现代软件项目的任何大型源库,它可能需要直接超过宇宙的生命周期,或者至少需要数千年。获得接近最优的解决方案是完全不同的事情(NP 与 P 问题),当前的编译器非常擅长这一点。
  • @Ped7g 甚至不可能创建最佳代码。主要是因为我们对“最优”没有一个好的定义。它是最小的代码吗?最快的代码?最小的内存消耗? “最快”是什么意思?最快的响应时间或吞吐量?
猜你喜欢
  • 2020-12-13
  • 2014-05-29
  • 1970-01-01
  • 2011-05-09
  • 1970-01-01
  • 2023-03-25
  • 2014-10-29
  • 2016-05-08
  • 2014-01-16
相关资源
最近更新 更多