函数栈帧:从创建到销毁,全程高能

1. 什么是函数栈帧

2. 理解函数栈帧能解决什么问题呢?

3. 函数栈帧的创建和销毁解析

3.1 什么是栈?

3.2 认识相关寄存器和汇编指令

3.3 解析函数栈帧的创建和销毁

3.3.1 预备知识

3.3.2 函数的调用堆栈

3.3.3 准备环境

3.3.4 转到反汇编

3.3.5 函数栈帧相关问题解答

1、什么是函数栈帧
我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数( 有的地方叫例行程序),所以C程序是以函数为基本单位的。那函数是如何调用的?函数的返回值又是如何待会的?函数参数是如何传递的?这些问题都和函数栈帧有关系。

函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间(这里我我的理解是函数调用过程中,所开辟的空间),这些空间是用来存放:

1.函数参数和函数返回值 
2.临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)
3.保存上下文信息(包括在函数调用前后需要保持不变的寄存器)

函数栈帧:从创建到销毁,全程高能_第1张图片

2、理解函数栈帧能解决什么问题

理解函数栈帧有什么用呢?

  只要理解了函数栈帧的创建和销毁,以下问题就能够很好的理解了:

 1.局部变量是如何创建的?

2.为什么局部变量不初始化内容是随机的?

3.函数调用时参数时如何传递的?传参的顺序是怎样的?

4.函数的形参和实参分别是怎样实例化的?

5.函数的返回值是如何带会的?

6。知道为什么函数有的时候逻辑没问题,但是会死循环

让我们一起走进函数栈帧的创建和销毁的过程中。

3.1首先先要明确什么是栈

在经典的计算机科学中,栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据弹出(出栈,pop),但是栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out, FIFO)。和数据结构中栈的使用规则相似。

入栈:push                  出栈pop

  • 在计算机系统中,栈帧是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小。
  • 在经典的操作系统中,栈总是向下增长由高地址向低地址的。栈区总是先使用高地址,再使用低地址。

3.2 认识相关寄存器和汇编指令

函数栈帧:从创建到销毁,全程高能_第2张图片

函数栈帧:从创建到销毁,全程高能_第3张图片

函数栈帧:从创建到销毁,全程高能_第4张图片

mov:数据转移指令
push:数据入栈,同时esp栈顶寄存器也要发生改变(压栈是在放在栈顶)

pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变
sub:减法命令
add:加法命令
call:函数调用,1. 压入返回地址 2. 转入目标函数
jump:通过修改eip,转入目标函数,进行调用
ret:恢复返回地址,压入eip,类似pop eip命令

3.3.1 预备知识

  1. 每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。
  2. 这块空间的维护是使用了2个寄存器: esp 和 ebp , ebp 记录的是栈底的地址, esp 记录的是栈顶的地址.

函数栈帧:从创建到销毁,全程高能_第5张图片

函数栈帧:从创建到销毁,全程高能_第6张图片

这两个图对比方便更好的理解,这些图也不是我原创,而是网上找的。

3.3.2 函数的调用堆栈

函数栈帧:从创建到销毁,全程高能_第7张图片

函数栈帧:从创建到销毁,全程高能_第8张图片

函数栈帧:从创建到销毁,全程高能_第9张图片

函数栈帧:从创建到销毁,全程高能_第10张图片

函数栈帧:从创建到销毁,全程高能_第11张图片

函数栈帧:从创建到销毁,全程高能_第12张图片

函数栈帧:从创建到销毁,全程高能_第13张图片

3.3.2 函数的调用堆栈

函数栈帧:从创建到销毁,全程高能_第14张图片

函数调用堆栈是反馈函数调用逻辑的,那我们可以清晰的观察到, main 函数调用之前,是由
invoke_main 函数来调用main函数。

函数栈帧:从创建到销毁,全程高能_第15张图片

函数栈帧:从创建到销毁,全程高能_第16张图片

函数栈帧:从创建到销毁,全程高能_第17张图片

3.3.3 准备环境

为了让我们研究函数栈帧的过程足够清晰,不要太多干扰,我们可以关闭下面的选项,让汇编代码中排
除一些编译器附加的代码:

函数栈帧:从创建到销毁,全程高能_第18张图片

函数栈帧:从创建到销毁,全程高能_第19张图片

以下是超详细的汇编解释代码:

00BE1820  push     ebp       //把ebp寄存器中的值进行压栈,此时的ebp中存放的是invoke_main函数栈帧的ebp,esp-4
00BE1821  mov     ebp,esp  //move指令会把esp的值存放到ebp中,相当于产生了main函数的ebp,这个值就是invoke_main函数栈帧的esp
00BE1823  sub     esp,0E4h  //sub会让esp中的地址减去一个16进制数字0xe4,产生新的esp,此时的esp是main函数栈帧的esp,此时结合上一条指令的ebp和当前的esp,ebp和esp之间维护了一个块栈空间,这块栈空间就是为main函数开辟的,就是main函数的栈帧空间,这一段空间中将存储main函数中的局部变量,临时数据已经调试信息等。
00BE1829  push     ebx       //将寄存器ebx的值压栈,esp-4
00BE182A  push     esi        //将寄存器esi的值压栈,esp-4
00BE182B  push     edi      //将寄存器edi的值压栈,esp-4
                                         //上面3条指令保存了3个寄存器的值在栈区,这3个寄存器的在函数随后执行中可能会被修改,所以先保存寄存器原来的值,以便在退出函数时恢复。
 
                                          //下面的代码是在初始化main函数的栈帧空间。
                                         //1. 先把ebp-24h的地址,放在edi中
                                         //2. 把9放在ecx中
                                       //3. 把0xCCCCCCCC放在eax中
                                       //4. 将从edp-0x2h到ebp这一段的内存的每个字节都初始化为0xCC
00BE182C  lea     edi,[ebp-24h] 
00BE182F  mov     ecx,9 
00BE1834  mov     eax,0CCCCCCCCh 
00BE1839  rep stos   dword ptr es:[edi]

上面的这段代码最后4句,等价于下面的伪代码

函数栈帧:从创建到销毁,全程高能_第20张图片

这是Add函数栈帧的创建与销毁了

int Add(int x, int y)
{
00BE1760  push     ebp        //将main函数栈帧的ebp保存,esp-4
00BE1761  mov     ebp,esp  //将main函数的esp赋值给新的ebp,ebp现在是Add函数的ebp
00BE1763  sub     esp,0CCh  //给esp-0xCC,求出Add函数的esp
00BE1769  push     ebx        //将ebx的值压栈,esp-4
00BE176A  push     esi        //将esi的值压栈,esp-4
00BE176B  push     edi        //将edi的值压栈,esp-4
int z = 0;   
00BE176C  mov     dword ptr [ebp-8],0     //将0放在ebp-8的地址处,其实就是创建z
z = x + y;
            //接下来计算的是x+y,结果保存到z中
00BE1773  mov     eax,dword ptr [ebp+8]  //将ebp+8地址处的数字存储到eax中
00BE1776  add     eax,dword ptr [ebp+0Ch]  //将ebp+12地址处的数字加到eax寄存中
00BE1779  mov     dword ptr [ebp-8],eax   //将eax的结果保存到ebp-8的地址处,其实就是放到z中
return z;
00BE177C  mov     eax,dword ptr [ebp-8]      //将ebp-8地址处的值放在eax中,其实就是把z的值存储到eax寄存器中,这里是想通过eax寄存器带回计算的结果,做函数的返回值。
}
00BE177F  pop     edi 
00BE1780  pop     esi 
00BE1781  pop     ebx 
00BE1782  mov     esp,ebp 
00BE1784  pop     ebp 
00BE1785  ret 
这些东西有些东西也不是原创,但是为了方便理解,我也整合了一下别人写的文章,我自己现在理解也不够,也是希望我自己也每天看一遍,只到融会贯通。

我找到一个完整过程图:

3.3.5 函数栈帧相关问题解答

函数栈帧:从创建到销毁,全程高能_第21张图片





                                                                                                                                                   完!

你可能感兴趣的:(java,开发语言,c#,c++,c语言,算法)