目录
什么是函数栈帧
理解函数栈帧能解决什么问题
函数栈帧的创建和销毁
什么是栈
认识相关寄存器和汇编指令
相关寄存器
相关汇编命令
esp和ebp
解析函数栈帧的创建和销毁
学前补充
函数的调用堆栈
什么是函数栈帧
- 函数参数和函数返回值
- 临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)
- 保存上下文信息(包括在函数调用前后需要保持不变的寄存器)
理解函数栈帧能解决什么问题
- 局部变量是如何创建的?
- 为什么局部变量不初始化内容是随机的?
- 函数调用时参数时如何传递的?传参的顺序是怎样的?
- 函数的形参和实参分别是怎样实例化的?
- 函数的返回值是如何带会的?
函数栈帧的创建和销毁
什么是栈
栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今看到的所有的计算机语言。在经典的计算机科学中,栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push ),也可将已经压入栈中的数据弹出(出栈,pop ),但是栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out , FIFO )。就像叠成一叠的书,先叠上去的书在最下面,因此要最后才能取出。在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小。 在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。 在我们常见的i386 或者 x86-64 下,栈顶由成为 esp 的寄存器进行定位的。
认识相关寄存器和汇编指令
相关寄存器
eax :通用寄存器,保留临时数据,常用于返回值ebx :通用寄存器,保留临时数据ebp :栈底寄存器esp :栈顶寄存器eip :指令寄存器,保存当前指令的下一条指令的地址
相关汇编命令
mov :数据转移指令push :数据入栈,同时 esp 栈顶寄存器也要发生改变pop :数据弹出至指定位置,同时 esp 栈顶寄存器也要发生改变sub :减法命令add :加法命令call :函数调用, 1 . 压入返回地址 2. 转入目标函数jump :通过修改 eip ,转入目标函数,进行调用ret :恢复返回地址,压入 eip ,类似 pop eip 命令
esp和ebp寄存器
当函数调用发生时,栈上会为新的函数分配一块新的空间,这个空间通常被称为栈帧(stack frame)。在维护帧栈时,`ebp` 和 `esp` 寄存器通常会指向当前活动函数的栈帧底部和顶部。
对于第一个空间(即当前活动函数的栈帧),`ebp` 通常会指向该空间的底部地址。而 `esp` 则是指向该空间顶部地址之上一个位置。也就是说,在只有一个空间时,它们都指向同一位置。
当发生另一个函数调用并创建了新的栈帧后,旧的 `ebp` 的值将被保存到新创建的栈帧中,并且更新为指向新创建栈帧底部地址。此时,早期创建出来但尚未完成执行(即尚未返回)的那个函数所对应之前保留在堆上内存中已经存在了两份数据:一份是原始状态下保存着早期生成代码段信息、静态变量等数据;另外一份则是现场保护压入堆盏信息以及局部变量等数据。
因此,在这种情况下:
- 早期生成代码段、静态变量等仍然可以通过原始 `ebp+偏移量1/2/3...n*4字节大小(32位系统)` 的方式访问
- 新创建的栈帧中的局部变量等可以通过新 `ebp+偏移量1/2/3...n*4字节大小(32位系统)` 的方式访问
结论:ebp和 esp 寄存器在维护帧栈时会指向当前活动函数的栈帧底部和顶部
我们也可以将两个寄存器叫做栈顶指针和栈底指针
解析函数栈帧的创建和销毁
学前补充
3. 函数栈帧的创建和销毁过程,在不同的编译器上实现的方法大同小异
函数的调用堆栈
#include <stdio.h> int Add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int a = 3; int b = 5; int ret = 0; ret = Add(a, b); printf("%d\n", ret); return 0; }
打开调试窗口的调用堆栈,发现在右侧的监视窗口中出现了__taminCRTStartup(),它表示在执行main函数时调用了__taminCRTStartup()这个堆栈,这就证明main也是被调用的,它在__taminCRTStartup()函数中被调用,此外,在监视窗口中还有一个函数mainCRTStartup(),__taminCRTStartup函数是在该函数中被调用的:
结论:main函数也是被其它函数调用的,当调用main函数之前就已经申请调用了两块空间,在mian函数中调用Add函数时,又会在main函数空间的上层为Add函数开辟一块空间,此时原本用于维护main函数的esp、ebp寄存器就会去维护Add函数栈帧的顶部和底部
~over~