我的 Ubuntu14.04 x86_64 系统上有这个 test.c。
void foo(int a, long b, int c) {
}
int main() {
foo(0x1, 0x2, 0x3);
}
我用 gcc --no-stack-protector -g test.c -o test
编译了这个并得到了汇编代码 objdump -dS test -j .text
00000000004004ed <_Z3fooili>:
void foo(int a, long b, int c) {
4004ed: 55 push %rbp
4004ee: 48 89 e5 mov %rsp,%rbp
4004f1: 89 7d fc mov %edi,-0x4(%rbp)
4004f4: 48 89 75 f0 mov %rsi,-0x10(%rbp)
4004f8: 89 55 f8 mov %edx,-0x8(%rbp) // !!Attention here!!
}
4004fb: 5d pop %rbp
4004fc: c3 retq
00000000004004fd <main>:
int main() {
4004fd: 55 push %rbp
4004fe: 48 89 e5 mov %rsp,%rbp
foo(0x1, 0x2, 0x3);
400501: ba 03 00 00 00 mov $0x3,%edx
400506: be 02 00 00 00 mov $0x2,%esi
40050b: bf 01 00 00 00 mov $0x1,%edi
400510: e8 d8 ff ff ff callq 4004ed <_Z3fooili>
}
400515: b8 00 00 00 00 mov $0x0,%eax
40051a: 5d pop %rbp
40051b: c3 retq
40051c: 0f 1f 40 00 nopl 0x0(%rax)
我知道函数参数应该按顺序从右到左压入堆栈。所以我很期待这个
void foo(int a, long b, int c) {
push %rbp
mov %rsp,%rbp
mov %edi,-0x4(%rbp)
mov %rsi,-0x10(%rbp)
mov %edx,-0x14(%rbp) // c should be push on stack after b, not after a
但是 gcc 似乎足够聪明,将参数 c(0x3) 推送到 a(0x1) 之后,以保存应该为 b(0x2) 的数据对齐保留的四个字节。有人可以解释一下这一点并向我展示一些关于 gcc 为什么这样做的文档吗?
最佳答案
参数在寄存器中传递 - edi
、esi
、edx
(然后是 rcx
、 r8
、r9
然后才压入堆栈) - 正是 Linux amd64 calling convention授权。
您在函数中看到的只是编译器在使用 -O0
编译时在输入时保存它们的方式,因此它们位于调试器可以修改它们的内存中。它可以自由地以任何它想要的方式来做,并且它巧妙地进行了这种空间优化。
这样做的唯一原因是,gcc -O0
总是在 C 语句之间溢出/重新加载所有 C 变量,以支持修改变量以及在函数中的行之间跳转调试器。
所有这些最终都会在发布版本中得到优化。
关于c - gcc 函数参数在堆栈帧上的对齐方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23513196/