编译时
#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
中?
最佳答案
如果 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
输出比优化代码更难阅读/理解/学习。映射回源代码更容易,但这是糟糕的代码。
关于c - 为什么调用 printf 会导致 main 的不同函数序言?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32896305/