函数调用是通过栈来实现的,而且知道在栈中存放着该函数的局部变量。但是,对于栈的实现细节可能不一定清楚。本文将介绍一下在Linux平台下函数栈是如何实现的。
栈帧的结构
函数在调用的时候都是在栈空间上开辟一段空间以供函数使用,栈是由高地址向地地址的方向生长的,而且栈有其栈顶和栈底,入栈出栈的地方就叫做栈顶。
在x86系统的CPU中,rsp是栈指针寄存器,这个寄存器中存储着栈顶的地址。rbp中存储着栈底的地址。函数栈空间主要是由这两个寄存器来确定的。
当程序运行时,栈指针rsp可以移动,栈指针和帧指针rbp一次只能存储一个地址,所以,任何时候,这一对指针指向的是同一个函数的栈帧结构。
而帧指针rbp是不移动的,访问栈中的元素可以用-4(%rbp)或者8(%rbp)访问%rbp指针下面或者上面的元素。
测试代码如下:
#include int sum (int a,int b) { int c = a + b; return c; } int main() { int x = 5,y = 10,z = 0; z = sum(x,y); printf("%drn",z); return 0; }
0000000000000000 : 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 89 7d ec mov %edi,-0x14(%rbp) # 参数传递 7: 89 75 e8 mov %esi,-0x18(%rbp) # 参数传递 a: 8b 55 ec mov -0x14(%rbp),%edx d: 8b 45 e8 mov -0x18(%rbp),%eax 10: 01 d0 add %edx,%eax 12: 89 45 fc mov %eax,-0x4(%rbp) # 局部变量 15: 8b 45 fc mov -0x4(%rbp),%eax # 存储结果 18: 5d pop %rbp 19: c3 retq 000000000000001a : 1a: 55 push %rbp # 保存%rbp。rbp,栈底的地址 1b: 48 89 e5 mov %rsp,%rbp # 设置新的栈指针。rsp 栈指针,指向栈顶的地址 1e: 48 83 ec 10 sub $0x10,%rsp # 分配 16字节栈空间。%rsp = %rsp-16 22: c7 45 f4 05 00 00 00 movl $0x5,-0xc(%rbp) # 赋值 29: c7 45 f8 0a 00 00 00 movl $0xa,-0x8(%rbp) # 赋值 30: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) # 赋值 37: 8b 55 f8 mov -0x8(%rbp),%edx 3a: 8b 45 f4 mov -0xc(%rbp),%eax 3d: 89 d6 mov %edx,%esi # 参数传递 ,从右向左 3f: 89 c7 mov %eax,%edi # 参数传递 41: e8 00 00 00 00 callq 46 # 调用sum 46: 89 45 fc mov %eax,-0x4(%rbp) 49: 8b 45 fc mov -0x4(%rbp),%eax # 存储计算结果 4c: 89 c6 mov %eax,%esi 4e: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 55 55: b8 00 00 00 00 mov $0x0,%eax 5a: e8 00 00 00 00 callq 5f 5f: b8 00 00 00 00 mov $0x0,%eax 64: c9 leaveq 65: c3 retq +0x45>+0x3b>+0x2c>
在函数被调用之前,调用者会为调用函数做准备。首先,函数栈上开辟了16字节的空间,存储定义的3个int型变量,建立了main函数的栈。
CALL指令内部其实还暗含了一个将返回地址(即CALL指令下一条指令的地址)压栈的动作(由硬件完成)。
具体来说,call指令执行时,先把下一条指令的地址入栈,再跳转到对应函数执行的起始处。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !