函数栈帧的小知识理解
栈帧这是一块基础而又容易被忽略的知识
1.寄存器(独立的,集成到cpu上)
ebp esp 存放地址 维护函数栈帧的
每一个函数调用,都要在栈创建一个空间,就是由ebp和esp维护两端的,包括main函数
简单通过一段代码来解释一下过程
#include <stdio.h>
void foo(int a, int b) {
int c = a + b;
printf("c = %d\n", c);
}
int main() {
int x = 5;
int y = 10;
foo(x, y);
return 0;
}
这是一个简单的调用算出两数之和的程序
首先我们进入main函数的时候,我们从vs的反汇编里去看
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 5
mov DWORD PTR [rbp-8], 10
mov eax, DWORD PTR [rbp-4]
mov esi, DWORD PTR [rbp-8]
mov edi, eax
call foo
mov eax, 0
leave
ret
首先我们push rbp的时候线保存我们main的指针
mov rbp, rsp设置当前帧指针
这样,我们的rbp,rsp指针都指向了main函数的栈顶
在接下来的 sub rsp, 16 这一步就是为我们的main函数开辟了16字节的空间
mov DWORD PTR [rbp-4], 5
mov DWORD PTR [rbp-8], 10
这两个move就把5和10压栈入我们的局部变量x和y中
mov eax, DWORD PTR [rbp-4]
:mov esi, DWORD PTR [rbp-8]
:- mov edi,eax
这是将我们的变量加载到寄存器中
call foo
:调用 foo
函数
接下来的foo函数里面
push rbp
:mov rbp, rsp
:sub rsp, 16
:
这里和main函数大同小异,都是保存指针,开辟栈帧空间
然后是把变量存储起来放入寄存器中计算,得出结果并且打印
leave
:ret
:
恢复调用者的帧指针,返回到调用者,把开辟的栈帧空间销毁
由于开辟地址是由高到低的,所以我们会发现如果下面我们在重复一次调用foo函数,里面变量的地址也是不变的
最后加上一点寄存器的总结
总结
eax
/rax
:用于存储函数的返回值。edi
/rdi
、esi
/rsi
:在 System V AMD64 ABI 调用约定中,用于传递函数的第一个和第二个参数。rcx
、rdx
:在 Microsoft x64 calling convention 调用约定中,用于传递函数的第一个和第二个参数。
理解好函数栈帧的调用对于我们的理解以及计算空间复杂度都是很有利的,多多尝试理解会有好处