路漫漫其修远兮,吾将上下而求索——屈原 《离骚》

Hello,各位热爱编程的宝子们,你们是否在追求真理的路上不懈奋斗呢?又是否有时候会因为无法达到自己的预期而苦恼呢?我想大家都会这样,但是每每想起屈原的离骚里的这句话,我又会拥有无穷无尽的力量。
哈哈哈,说了这么多,其实我今天的真正目的是向大家分享我最近攻克的一大难题,真的是cpu差点都给我榨干了啊,真的是可谓不吐不快。这就是函数栈帧的创建和销毁。
说到这里,很多人可能会问:什么是函数栈帧呢?它又是如何创建,又是如何销毁的呢?
函数栈帧的定义:是指在函数调用过程中,系统为该函数在内存的栈区区域中开辟的一段储存空间。这个空间用于存储函数的局部变量、参数、返回地址等,以便函数的执行和管理。
函数栈帧的组成:
1、返回地址:函数执行完毕后需要返回调用处的地址,即下一条指令的地址。
2、帧指针:指向·该函数的栈帧底部,用于访问局部变量和函数参数。
3、局部变量:函数执行时需要使用的变量。
4、参数:被调用函数需要接收的参数。
5、临时变量:某些函数中可能需要使用的临时变量。
创建和销毁过程:当函数被调用时,系统会为其创建一个新的栈帧,并将其压入调用栈中。创建过程包括分配内存、设置局部变量和参数、设置返回地址等步骤。当函数执行完毕并准备返回时,其对应的栈帧被销毁,包括释放内存和恢复栈指令等步骤。
下面就让我给大家举一个例子:

图一
看完这张图片,相信大家一眼就能看出这其实就是一次加法函数的声明与调用。在函数运行完毕且没有错误之后,我们先按F10进行逐过程调试(需注意我这里使用的是VS2022版本),然后再点击鼠标右键转到反汇编,就可以看到我们的反汇编码了。
现在我们来进行一 一分析,首先我们经过头文件后进入到mian函数

图二
mian函数过后,我们就进入到·自定义Add函数中

图三
Add函数过后,就到了输出结果

图四
怎么样?相信大家看完大致过程,肯定还是一脸懵逼吧?哈哈哈。但是问题不大,我先给大家把细枝末节的地方分析一下。以上图片中,eax、ebx、ecx、edx、ebp、esp都代表不同的寄存器,但是ebp、esp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。push代表压栈,pop代表出栈,move代表栈移动,sub代表栈减少,lea代表导入有效地址,dword ptr代表把后者放入前者。下面是我在函数栈帧的创建和销毁过程中画的过程图:

图五
接下来我将会按照图二,图三,图四的顺序给大家分析。
首先进入内存我们先单独划分一块空间 esp为栈顶指针,ebp为栈底指针,然后通过move指令,将esp的位置赋给ebp,再将esp向低地址移动0E4h个空间,接着再通过push指令分别压栈ebx、esi、edi、,然后经过lea指令将ebp-24h的有效地址赋给edi,最后后面五排的统一作用就是给main函数开辟一块全是cccccccc的栈帧空间,然后我们定义了a和b,我们把1(也就是a)放在了ebp-8的位置,把2(也就是b)放在了ebp-14h的位置,最后呢,又分别把ebp-8和ebp-14(也就是a和b)分别赋给了ecx和eax。(图二分析)
main函数栈帧空间开辟好之后呢,我们就进入到Add函数,注意在经过mian函数的栈帧开辟过程中,esp和ebp的实际位置已经发生改变。但是在Add函数中,其实发生了和在main函数中一样的指令(分析略过),但是eax,dword ptr [ebp-14h]就已经把a和b相加了。(图三分析)
Add函数栈帧空间创建好之后呢,在经过一系列pop出栈操作,就进行了函数栈帧的销毁,最后在栈空间的顶部就只剩下返回值ret对于3。(图三分析)
好了,在经过我的逐一分析之后,大家是否感觉自己对函数栈帧的认知更加清晰可观了呢?但是,我始终感觉我讲解的时候略有不足之处,大家如果有什么不懂或者独到的见解,也希望大家可以给我一些好的建议或者和我积极讨论。因为没有问题就是最大的问题,一个人的成功,需要大家的质疑和帮助,谢谢啦!
#include <stdio.h>
int Add(int m, int n)
{
return m + n;
}
int main()
{
int a = 1;
int b = 2;
int ret = Add(a, b);
printf("%d\n", ret);
return 0;
}
1806

被折叠的 条评论
为什么被折叠?



