编译时
#include <stdio.h>
int
main () {
return 0;
}
到x86程序集,结果是明确的和预期的:
$> cc -m32 -S main.c -o -|sed -r "/\s*\./d"
main:
pushl %ebp
movl %esp, %ebp
movl $0, %eax
popl %ebp
ret
但是在研究不同的反汇编二进制文件时,函数序言从未如此简单。确实,将上面的C源改为
#include <stdio.h>
int
main () {
printf("Hi");
return 0;
}
结果是
$> cc -m32 -S main.c -o -|sed -r "/\s*\./d"
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $4, %esp
subl $12, %esp
call printf
addl $16, %esp
movl $0, %eax
movl -4(%ebp), %ecx
leave
leal -4(%ecx), %esp
ret
特别是,我不明白这些说明的原因
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
生成 - 特别是为什么不直接将%esp
存储到%ecx
而不是%esp+4
?
答案 0 :(得分:4)
如果main
不是叶子函数,它需要对齐堆栈以获得它调用的任何函数。不被称为main
的函数只是保持堆栈的对齐。
lea 4(%esp), %ecx # ecx = esp+4
andl $-16, %esp
pushl -4(%ecx) # load from ecx-4 and push that
它正在推送一个返回地址的副本,因此在对齐堆栈后它将位于正确的位置。你是对的,不同的序列会更明智:
mov (%esp), %ecx ; or maybe even pop %ecx
andl $-16, %esp
push %ecx ; push (mem) is slower than push reg
正如Youka在评论中所说,不要期望-O0
的代码在所有中优化。使用-Og
进行不会干扰可调试性的优化。 gcc手册建议用于编译/调试/编辑循环。 -O0
输出比优化代码更难阅读/理解/学习。它可以更容易地映射到源代码,但它的代码很糟糕。