函数栈帧的创建和销毁_mov exc,35岁老年程序员的绝地翻身之路

89 阅读8分钟

​编辑3.3 分析main()函数初始化和Add函数的传参

3.4Add函数的传参

3.5函数栈帧的销毁

4. 总结


对于初学c语言而言,理解好函数栈帧的创建和销毁,才能有效地编写代码,看似代码更是内存。就好比修炼内功,看似普攻实质是法伤。

1.什么是函数栈帧

那么是函数栈帧呢?

我们在编写代码时,对函数的调用过程,在这个过程中为函数开辟栈空间

这些空间是用来存放:函数参数和函数返回值  临时变量  保存上下文信息 

这块栈空间我们称之为函数栈帧

2.学习之前的基础知识介绍

2.1什么是栈?

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

2.2创建与销毁

创建:初始化栈。构造一个空栈s,分配内存空间。

销毁:销毁栈。销毁并释放栈s所占用的内存空间。

2.3相关寄存器和汇编指令

相关寄存器

eax:通用寄存器,保留临时数据,常用于返回值 ebx:通用寄存器,保留临时数据 ebp:栈底寄存器 esp:栈顶寄存器 eip:指令寄存器,保存当前指令的下一条指令的地址

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

特别注意

ebp(栈底寄存器)和esp(栈顶寄存器)他们都是作为访问栈堆的指针,ebp和esp都是联合使用,主要用于存放堆栈内存储单元的偏移量,用它们可实现多种存储器操作数的寻址方式,为以不同的地址形式访问存储单元提供方便。

mov 的用法例子 mov exc,exd ;   exd->exc  exc =00000034H  edx =00000052H                            指令执行结果: ecx = 00000052H, edx不变,标志寄存器也不变。

push和pop:在mian栈帧中,创建空间是从由高地址向低地址增加 push是在向低地址方向压栈(-地址)  而pop这相反 往高地址方向(+地址)

call dword ptr + 内存单元地址:在本次操作中用于进入add函数的调用,并且将call指令的下一条指令的IP入栈,然后跳到以内存单元的高位为CS,低位为IP的代码处。 word为2字节 double word为4字节

3.演示函数栈帧的创建和销毁的过程

3.1演示代码

#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;
}

3.2演示环境

本次演示是在vs2012编译器器下进行调试 先按F10 再点击右键选择反汇编 如图所示

3.3分析main()函数开辟栈帧

007E1420 55                   push        ebp 
 //减少4字节,把ebp寄存器中的值进行压栈,此时的ebp中存放的是invoke_main函数栈帧的ebp,esp-4
007E1421 8B EC                mov         ebp,esp  
//move指令会把esp的值存放到ebp中,这里他们同时存在一个单元中
007E1423 81 EC E4 00 00 00    sub         esp,0E4h  
//对esp减去一个16进制的数字0xe4 就是向低地址移动了0E4h字节 这里0E4h转化成10进制为78
007E1429 53                   push        ebx  
007E142A 56                   push        esi  
007E142B 57                   push        edi  
//对寄存器ebx esi edi 进行压栈 都是以4字节减少
007E142C 8D BD 1C FF FF FF    lea         edi,[ebp-0E4h]  
//先把ebp-24h的地址,放在edi中
007E1432 B9 39 00 00 00       mov         ecx,39h  
// 把9放在ecx中
007E1437 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
// 把0xCCCCCCCC放在eax中
007E143C F3 AB                rep stos    dword ptr es:[edi] 
//将从edp-0x2h到ebp这一段的内存的每个字节都初始化为0xCC
 

注意:这里我们先不用去考虑mainCRTStartup与__tmainCRTStartup的由来 ,我们只需要明白main()函数是被__tmainCRTStartup函数调用的,而 __tmainCRTStartup又是被mainCRTStartup调用的即可。

3.3 分析main()函数初始化和Add函数的传参

int a = 3;
007E143E  mov         dword ptr [ebp-8],3  
//将3存储到ebp-8的地址处,ebp-8的位置其实就是a变量
 int b = 5;
007E1445  mov         dword ptr [ebp-14h],5 
//将5存储到ebp-14h的地址处,ebp-14h的位置其实就是b变量
 int ret = 0;
007E144C  mov         dword ptr [ebp-20h],0  
//将0存储到ebp-20h的地址处,ebp-20h的位置其实就是ret变量
//以上就是对a,b,ret在main()函数栈帧中进行初始化
 ret = Add(a, b);
//调用Add函数时的传参 将参数压栈到栈帧空间 
007E1453 8B 45 EC             mov         eax,dword ptr [b]  
007E1456 50                   push        eax  
007E1457 8B 4D F8             mov         ecx,dword ptr [a]  
007E145A 51                   push        ecx  
//以上是 传值b 将ebp-14h处放的5放在eax寄存器中 再把eax的值压栈,esp-4 
//传值a 将将ebp-8处放的3放在ecx寄存器中 再把ecx的值进行压栈,esp-4
007E145B E8 86 FC FF FF       call        _Add (07E10E6h)  
007E1460 83 C4 08             add         esp,8  
007E1463 89 45 E0             mov         dword ptr [ret],eax  

//跳转调用add函数 
//call指令是要执行函数调用逻辑的,在执行call指令之前先会把call指令的下一条指令的地址进行压栈
操作,这个操作是为了解决当函数调用结束后要回到call指令的下一条指令的地方,继续往后执行。

 

 3.4Add函数的传参

int Add(int x, int y)
{
007E13D0  push        ebp  
007E13D1  mov         ebp,esp  
007E13D3  sub         esp,0CCh  
007E13D9  push        ebx  
007E13DA  push        esi  
007E13DB  push        edi  
007E13DC  lea         edi,[ebp+FFFFFF34h]  
007E13E2  mov         ecx,33h  
007E13E7  mov         eax,0CCCCCCCCh  
007E13EC  rep stos    dword ptr es:[edi]  
 int z = 0; 
007E13EE  mov         dword ptr [ebp-8],0  
//这前面和mian()函数基本差不多 由栈顶指针和栈底指针开辟栈帧空间,再进行初始化
//将低地址esp-0xCC到高地址edp重复33次初始为0xcccccccc  再将0放在edp-8处,创建z
z = x + y;
007E13F5  mov         eax,dword ptr [ebp+8]  
//将ebp+8地址处的数字存储到eax中
007E13F8  add         eax,dword ptr [ebp+0Ch]  
//将ebp+12地址处的数字加到eax寄存中
007E13FB  mov         dword ptr [ebp-8],eax  
//将eax的结果保存到ebp-8的地址处,其实就是放到z中
 return z;
007E13FE  mov         eax,dword ptr [ebp-8]  
//将ebp-8地址处的值放在eax中,其实就是把z的值存储到eax寄存器中,这里是想通过eax寄存器带回计算的结果,做函数的返回值。

}

收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。 img img

如果你需要这些资料,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!