什么是函数栈帧
我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。
那函数是如何调用的?函数的返回值又是如何待会的?函数参数是如何传递的?这些问题都和函数栈帧有关系。
函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放:
- 函数参数和函数返回值
- 临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)
- 保存上下文信息(包括在函数调用前后需要保持不变的寄存器)。
什么是栈?
栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函 数,没有局部变量,也就没有我们如今看到的所有的计算机语言。
与函数栈帧有关的汇编语句
eax:通用寄存器,保留临时数据,常用于返回值
ebx:通用寄存器,保留临时数据
ebp:栈底寄存器
esp:栈顶寄存器
eip:指令寄存器,保存当前指令的下一条指令的地址
mov:数据转移指令
push:数据入栈,同时esp栈顶寄存器也要发生改变
pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变
sub:减法命令
add:加法命令
call:函数调用,1. 压入返回地址 2. 转入目标函数
jump:通过修改eip,转入目标函数,进行调用
ret:恢复返回地址,压入eip,类似pop eip命令(返回子程序)
函数如何创建栈帧并销毁
当程序进入main函数时,要给main函数在栈区创建空间,esp(栈顶)和ebp(栈底)对main函数进行维护
当程序执行时,我们在调试窗口对堆栈段进行调用,我们可以看到main函数是被__tmainSRTStartup函数所调用,说明main函数是它的内部函数,而__tmainSRTStartup又是被mainCRStartup这个函数调用
梳理一下上面的思路
这些函数在堆栈当中的存储
main函数栈帧开辟
接下来重新调试,我们转到反汇编
调用main函数之前,esp和ebp对调用main函数的函数进行维护 ,当栈顶发生改变时,esp会指向新的栈顶
003118B0 push ebp
003118B1 mov ebp,esp
先把ebp入栈,然后把ebp移动到esp的位置
003118B3 sub esp,0E4h
003118B9 push ebx
003118BA push esi
003118BB push edi
之后又把esp往上移动,移动完把ebx,esi,edi压栈,esp和ebp现在指的这块空间是为main函数预先开辟好的
把edi-0EFH也就是main函数开始的这里的地址,放到edi里面去,然后从这个地址开始赋值39次的双字节数据,赋值为CCCC
到这里main函数栈帧开辟完成
接下来就是在main函数的空间里,创建三个变量,并给赋值
调用Add函数
对函数进行传参,创建俩个临时变量,然后压栈进去
接下来进入call开始调用函数call此时的地址是00C2144B
此时按下F11,我们发现call指令的下一条地址被压到了栈区
把call的下一个地址压栈,ps:后面会用到这条指令,可先放在这不管
接下来进入Add函数,跟前面main函数一样,先开辟空间,然后赋值为CCCCCC,再为变量在函数里创建空间并赋值
接下来执行加法运算,由于刚才已经创建好了零时变量,所以把他俩进行相加,加完之后把结果传过来就行,传过来之后把这个值放在eax里面去
返回主函数
按顺序出栈,之后把ebp赋值给esp
之后pop,ebp把ebp进行出栈,ebp便回到main函数这里,ebp此时回到这里,esp也自然而然的往下指一个,ret指令是返回,然后esp来到了call指令的下一条指令
把栈顶指针弹出去,esp自然向下指一条
之后给esp加8即释放这俩个临时变量
之后把eax放到ebp-20h,eax是存放刚才加法和的地方
到此这篇关于C语言函数栈帧的创建与销毁原理图解的文章就介绍到这了,更多相关C语言函数栈帧内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!