我正在尝试了解函数调用期间堆栈中将出现的内容。
据我所知,在调用另一个函数之前,调用者的参数(如果有的话),调用者的返回地址和基地址将被压入堆栈。
所以,我写了一个简单的C程序
#include <stdio.h>
void
foo()
{
}
int
main()
{
foo();
return 0;
}
和相应的拆解机器代码
08048334 <foo>:
8048334: 55 push %ebp
8048335: 89 e5 mov %esp,%ebp
8048337: c9 leave
8048338: c3 ret
08048339 <main>:
8048339: 55 push %ebp
804833a: 89 e5 mov %esp,%ebp
804833c: 83 ec 08 sub $0x8,%esp
804833f: 83 e4 f0 and $0xfffffff0,%esp
8048342: b8 00 00 00 00 mov $0x0,%eax
8048347: 83 c0 0f add $0xf,%eax
804834a: 83 c0 0f add $0xf,%eax
804834d: c1 e8 04 shr $0x4,%eax
8048350: c1 e0 04 shl $0x4,%eax
8048353: 29 c4 sub %eax,%esp
8048355: e8 da ff ff ff call 8048334 <foo>
804835a: b8 00 00 00 00 mov $0x0,%eax
804835f: c9 leave
8048360: c3 ret
8048361: 90 nop
8048362: 90 nop
8048363: 90 nop
虽然foo()的代码有意义,但我无法理解main()代码。为什么有这么多的操作?我只期待main()
中的以下操作 1. Push the frame pointer
2. Call foo (which will inturn save the return address)
有人可以解释一下main()的代码吗?谢谢!
答案 0 :(得分:0)
在x86上(您可能将其标记为),ABI(应用程序二进制接口)要求在调用函数时将堆栈与某些边界(在本例中为16个字节)对齐。因此,当main()
想要调用foo()
时,首先必须对齐堆栈指针。
答案 1 :(得分:0)
主要的前三行
8048339: 55 push %ebp
804833a: 89 e5 mov %esp,%ebp
804833c: 83 ec 08 sub $0x8,%esp
被称为function prologue。这组指令将基指针推送到堆栈,然后为基指针分配当前堆栈的值,从而创建新的堆栈帧。然后减少堆栈指针以为函数的局部变量保留空间(由于调用约定,您仍然没有这样做但仍然完成)。下一条指令
804833f: 83 e4 f0 and $0xfffffff0,%esp
将堆栈与下一个较低的16字节边界对齐。以下说明
8048342: b8 00 00 00 00 mov $0x0,%eax
8048347: 83 c0 0f add $0xf,%eax
804834a: 83 c0 0f add $0xf,%eax
804834d: c1 e8 04 shr $0x4,%eax
8048350: c1 e0 04 shl $0x4,%eax
8048353: 29 c4 sub %eax,%esp
已经在SE上出现了几次(here,如Paul R所指出的,以及here和again here)。这个例程似乎在堆栈上保留了额外的空间,但是以一种奇怪的无效方式执行此操作。本节可能取决于gcc版本和操作系统,似乎没有必要。
其余的指令调用foo并退出程序。