函数栈帧的创建与销毁

目录

目录

1.补充说明

2.举例代码

3.main函数栈帧空间的开辟

4.Add函数栈帧空间的开辟

5.Add函数栈帧空间的出栈

6.返回main函数栈帧空间



1.补充说明

压栈   push   :给栈顶放一个元素

出栈   pop  :从栈顶取出一个元素

寄存器:电脑中的任何指令都是在CPU上的运行的,但是CPU本身只负责运算不负责存储,数据一般都是存储在内存和寄存器中。常见的寄存器有:eax , ebx , ecx , edx ; ebp , esp

ebp , esp : 这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。

注:在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。这里使用的环境是vs2013(汇编语言)

注:在vs2013中,main函数也是被其他函数调用的。调用顺序是:

mainCRTStartup   ->   __tmainCRTStartup   ->   main


2.举例代码

接下来会以下面这段代码为例,以汇编语言的方式逐句去分析上面的问题。

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}

int main()
{
	int a = 10;

	int b = 20;

	int c = Add(a, b);

	printf("%d\n", c);

	return 0;
}

汇编语言:

int main()
{
00B11410  push        ebp  
00B11411  mov         ebp,esp  
00B11413  sub         esp,0E4h  
00B11419  push        ebx  
00B1141A  push        esi  
00B1141B  push        edi  
00B1141C  lea         edi,[ebp+FFFFFF1Ch]  
00B11422  mov         ecx,39h  
00B11427  mov         eax,0CCCCCCCCh  
00B1142C  rep stos    dword ptr es:[edi]  
	int a = 10;
00B1142E  mov         dword ptr [ebp-8],0Ah  

	int b = 20;
00B11435  mov         dword ptr [ebp-14h],14h  

	int c = Add(a, b);
00B1143C  mov         eax,dword ptr [ebp-14h]  
00B1143F  push        eax  
00B11440  mov         ecx,dword ptr [ebp-8]  
00B11443  push        ecx  
00B11444  call        00B110E1  
00B11449  add         esp,8  
00B1144C  mov         dword ptr [ebp-20h],eax  

	printf("%d\n", c);
00B1144F  mov         esi,esp  
00B11451  mov         eax,dword ptr [ebp-20h]  
00B11454  push        eax  
00B11455  push        0B15858h  
00B1145A  call        dword ptr ds:[00B19114h]  
00B11460  add         esp,8  
00B11463  cmp         esi,esp  
00B11465  call        00B1113B  

	return 0;
00B1146A  xor         eax,eax  
}
00B1146C  pop         edi  
00B1146D  pop         esi  
00B1146E  pop         ebx  
00B1146F  add         esp,0E4h  
00B11475  cmp         ebp,esp  
00B11477  call        00B1113B  
00B1147C  mov         esp,ebp  
00B1147E  pop         ebp  
00B1147F  ret  
#include<stdio.h>


int Add(int x, int y)
{
00B113C0  push        ebp  
00B113C1  mov         ebp,esp  
00B113C3  sub         esp,0CCh  
00B113C9  push        ebx  
00B113CA  push        esi  
00B113CB  push        edi  
00B113CC  lea         edi,[ebp+FFFFFF34h]  
00B113D2  mov         ecx,33h  
00B113D7  mov         eax,0CCCCCCCCh  
00B113DC  rep stos    dword ptr es:[edi]  
	int z = 0;
00B113DE  mov         dword ptr [ebp-8],0  
	z = x + y;
00B113E5  mov         eax,dword ptr [ebp+8]  
00B113E8  add         eax,dword ptr [ebp+0Ch]  
00B113EB  mov         dword ptr [ebp-8],eax  
	return z;
00B113EE  mov         eax,dword ptr [ebp-8]  
}
00B113F1  pop         edi  
00B113F2  pop         esi  
00B113F3  pop         ebx  
00B113F4  mov         esp,ebp  
00B113F6  pop         ebp  
00B113F7  ret  


3.main函数栈帧空间的开辟

int main()
{
00B11410  push        ebp // 指令:将ebp的值压栈到 __tmainCRTStartup 函数栈帧空间上

                          //这时esp会向上移动,因为是栈顶指针,栈顶指针一直指向的是栈顶,每次进 
                          //行压栈,esp都会指向栈的最上方

                          //注:地址是由高到低的,所以这里esp的值是减少,因为是整型,所以这里减 
                          //少了4个字节。

                          //在内存中可以看出来ebp的值被压进去了,倒着读就是  00 af  f8 f4 

       


00B11411  mov         ebp,esp  //指令:把  ebp 移动到  esp
                               //注:mov:移动。



00B11413  sub         esp,0E4h  //指令: 给esp减去0E4H,
                                //sub:减去

                                //0E4H是八进制数字   ==  0x000000e4  ==   228(十进制)

注:这里相当于给main函数预分配栈帧空间


00B11419  push        ebx  // 指令:将ebx的值压栈到 main 函数栈帧空间上       
00B1141A  push        esi  // 指令:将esi的值压栈到 main 函数栈帧空间上
00B1141B  push        edi  // 指令:将edi的值压栈到 main 函数栈帧空间上


00B1141C  lea         edi,[ebp+FFFFFF1Ch]  //指令:把 [ebp+FFFFFF1Ch] 加载到 edi 
                                           //[ebp+FFFFFF1Ch] == [ebp-0E4h] 

                                           //lea:load effecitve  address  加载有效地址
00B11422  mov         ecx,39h // 指令:把ecx置成39h
00B11427  mov         eax,0CCCCCCCCh //指令:把eax置成0CCCCCCCCh

00B1142C  rep stos    dword ptr es:[edi]  //指令:从 edi 开始,向下的 ecx 的空间数据全部置成 
                                          //eax

                                          //dword : double word   ==  4个字节

 

注:这里相当于main函数栈帧初始化成 cc


int a = 10;
00B1142E  mov         dword ptr [ebp-8],0Ah  //指令:把  0Ah  放进  ebp-8 的位置

                                             //0Ah (八进制) ==   0x0000000a(十六进制)    
                                             //==  10(十进制)

	int b = 20;
00B11435  mov         dword ptr [ebp-14h],14h //指令:同上

 


	int c = Add(a, b);
00B1143C  mov         eax,dword ptr [ebp-14h]  //指令:把 ebp-14h 的值放进 eax

00B1143F  push        eax //指令:将 eax 的值压栈到 main 函数栈帧空间上

00B11440  mov         ecx,dword ptr [ebp-8] // 指令:把 ebp-8 的值放进 ecx

00B11443  push        ecx  //指令:将 ecx 的值压栈到 main 函数栈帧空间上

 注:这里相当于传参


00B11444  call        00B110E1 //指令:call指令是把call指令下一条指令的地址压栈到main函数栈帧 
                               //空间上

 注:这里的作用是调用完成Add函数后,跳回到call指令的下一条指令


4.Add函数栈帧空间的开辟

int Add(int x, int y)
{
00B113C0  push        ebp  //这里是main函数的ebp
00B113C1  mov         ebp,esp  
00B113C3  sub         esp,0CCh  
00B113C9  push        ebx  
00B113CA  push        esi  
00B113CB  push        edi  
00B113CC  lea         edi,[ebp+FFFFFF34h]  
00B113D2  mov         ecx,33h  
00B113D7  mov         eax,0CCCCCCCCh  
00B113DC  rep stos    dword ptr es:[edi]  //同main函数创建栈帧空间相同

	int z = 0;
00B113DE  mov         dword ptr [ebp-8],0  //指令:把 0 放进 ebp-8

 


	z = x + y;
00B113E5  mov         eax,dword ptr [ebp+8]    //指令:ebp+8  的值放进  eax
00B113E8  add         eax,dword ptr [ebp+0Ch]  //指令:ebp+0Ch 的值 加上 eax 
00B113EB  mov         dword ptr [ebp-8],eax    //指令:eax 的值放进 ebp-8



注:这里的x,y是找到之前压栈进来的值进行计算,所以说形参是实参的一份临时拷贝


	return z;
00B113EE  mov         eax,dword ptr [ebp-8]  //指令:把 ebp - 8 的值放进 eax

问题:局部变量 z 出作用域销毁,为什么在这里能把返回值传给main函数?

答:因为这里的值传给了寄存器,寄存器出程序时不会销毁

​​​​​​​​​​​​​​5.Add函数栈帧空间的出栈

0B113F1  pop         edi  //弹出
00B113F2  pop         esi   //弹出
00B113F3  pop         ebx   //弹出
  

00B113F4  mov         esp,ebp  //指令:把  ebp  赋值给  esp

00B113F6  pop         ebp  //指令:弹出 

这里弹出 ebp 之后,ebp 回到之前指向main函数的栈底

00B113F7  ret  //指令:ret 返回的时候,从栈顶弹出了 call指令下一条指令的地址 ,然后跳到那个位置


6.返回main函数栈帧空间

00B11449  add         esp,8 //指令:esp + 8

 注:这里形参其实就已经销毁了

00B1144C  mov         dword ptr [ebp-20h],eax  //指令:把  eax  的值放进  ebp-20h


这里函数的栈帧的创建与销毁就已经解释清楚了,具体需要结合图片一步步跟随汇编语言即可

①局部变量是怎样创建的?

       局部变量的创建首先为函数分配好栈帧空间,然后初始化一部分空间,然后在这个栈帧中给局部变量分配空间。

②为什么局部变量的值是随机值?

      因为函数栈帧在初始化的时候会给栈帧空间随机赋值

③函数是怎样传参?传参的顺序是怎样的?

      从右向左开始压栈压进去,通过指针的方式找到形参

④形参和实参是什么关系?

   形参是在压栈时候开辟的空间,它和实参只是值是相同的,空间是独立的,   所以形参是实参的    一份临时拷贝,改变形参不会影响实参

⑤函数调用是怎样实现的?

⑥函数调用结束后是怎样返回的?

     call指令下一条指令的地址被压进栈帧中,弹出的时候会正常返回到上一个函数

     返回值是通过寄存器的方式带回的


注:以上仅代表个人记录与分享,如有错误欢迎指正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值