函数栈帧的创建和销毁

前言

对于编程这件事来说,函数的调用在代码中是必不可少的,那函数在内存中到底是如何创建和销毁的,下面我将写一个简单的函数为大家演示。

为了更加清楚的理解函数栈帧的创建和销毁,将使用VS2013为大家演示(不同版本的vs演示的效果可能不同),

一 认识寄存器

根据百度百科介绍,寄存器是中央处理器内的组成部分。 寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。 简单来说, 寄存器就是存放东西的 。 从名字来看,跟火车站寄存行李的地方好像是有相似。

在本次函数栈帧的创建和销毁要到寄存器有:

eax

ebx

ecx

edx

esp:栈顶指针

ebp:栈底指针

对于这四个寄存器知道有这些寄存器即可,对于esp和ebp是用来维护函数的。

二 Add函数栈帧的创建和销毁

#define  _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int Add(int x, int y)
{
	int c = 0;
	c = x + y;
	return c;
}
int main()
{
	int a = 10;
	int b = 20;
	int ret = Add(a, b);
	printf("ret = %d\n", ret);
	return 0;
}

在调试之前,我们要想一个问题,main函数是程序的入口,但mian函数也是一个函数,那么它又是被什么函数调用的呢?

我们可以调用堆栈进行观察:

这时我们发现main函数是被__tmainCRTStartup函数调用,而__tmainCRTStartup函数又是被mainCRTStartup函数调用。

main函数的调用

int main()
{
00881410  push        ebp  
00881411  mov         ebp,esp  
00881413  sub         esp,0E4h  
00881419  push        ebx  
0088141A  push        esi  
0088141B  push        edi  
0088141C  lea         edi,[ebp+FFFFFF1Ch]  
00881422  mov         ecx,39h  
00881427  mov         eax,0CCCCCCCCh  
0088142C  rep stos    dword ptr es:[edi]  
	int a = 10;
0088142E  mov         dword ptr [ebp-8],0Ah  
	int b = 20;
00881435  mov         dword ptr [ebp-14h],14h  
	int ret = Add(a, b);
0088143C  mov         eax,dword ptr [ebp-14h]  
0088143F  push        eax  
00881440  mov         ecx,dword ptr [ebp-8]  
00881443  push        ecx  
00881444  call        008810E1  
00881449  add         esp,8  
0088144C  mov         dword ptr [ebp-20h],eax  
	printf("ret = %d\n", ret);
0088144F  mov         esi,esp  
00881451  mov         eax,dword ptr [ebp-20h]  
00881454  push        eax  
	printf("ret = %d\n", ret);
00881455  push        885858h  
0088145A  call        dword ptr ds:[00889114h]  
00881460  add         esp,8  
00881463  cmp         esi,esp  
00881465  call        0088113B  
	return 0;
0088146A  xor         eax,eax  
}
0088146C  pop         edi  
0088146D  pop         esi  
0088146E  pop         ebx  
0088146F  add         esp,0E4h  
00881475  cmp         ebp,esp  
00881477  call        0088113B  
0088147C  mov         esp,ebp  
0088147E  pop         ebp  
0088147F  ret  

这些都是调用main函数的准备工作

其中的push ebp指的是压栈,就是把ebp中的指向的值给ebp。

 mov ebp,esp.这里是指把esp中的值给ebp

 sub         esp,0E4h 是指把esp中的值减去0E4h。

 push        ebx  
push        esi  
 push        edi

压栈三个值

 

  lea         edi,[ebp-0E4h]  
 mov         ecx,39h  
 mov         eax,0CCCCCCCCh  
rep stos    dword ptr es:[edi]  

这些反汇编就是将mian函数的值,从edi开始下面初始化39次,都初始化为CCCCCCCC

所以,为什么有时候会打印出 烫烫烫烫烫烫,因为变量在初始化时内存里面放的CCCCCC的值。

    int a = 10;
 mov         dword ptr [ebp-8],0Ah  

这里代码的意思是将a的值放到ptr地址处

mov         dword ptr [ebp-8],0Ah  
    int b = 20;

同理是将b的值放到[ebp-8]的地址处

 下面就进行函数的传参,那函数传参又是在内存中进行的呢?

mov         eax,dword ptr [ebp-14h]  

这里就是将[ebp-14h](b的值)中的值放到eax中

push        eax 

将eax进行压栈

mov         ecx,dword ptr [ebp-8]  

这里就是将[ebp-14h](a的值)中的值放到ecx中

push        ecx

将ecx进行压栈

 

 下面就进行函数调用

00881444  call        008810E1  

 为了调用的函数参数能够返回,call指令下一条指令的地址。

 下面进入到Add函数中的反汇编。

下面这些又是在干什么呢?其实是在维护Add函数的栈帧。

同main函数的维护一样,都是要函数栈帧的准备工作,分配内存,初始化内存。CCCCCCCCCCCCC

int Add(int x, int y)
{
008813C0  push        ebp  
008813C1  mov         ebp,esp  
008813C3  sub         esp,0CCh  
008813C9  push        ebx  
008813CA  push        esi  
008813CB  push        edi  
008813CC  lea         edi,[ebp+FFFFFF34h]  
008813D2  mov         ecx,33h  
008813D7  mov         eax,0CCCCCCCCh  
008813DC  rep stos    dword ptr es:[edi]  
	int c = 0;
008813DE  mov         dword ptr [ebp-8],0  
	c = x + y;
008813E5  mov         eax,dword ptr [ebp+8]  
008813E8  add         eax,dword ptr [ebp+0Ch]  
008813EB  mov         dword ptr [ebp-8],eax  
	return c;
008813EE  mov         eax,dword ptr [ebp-8]  

 那函数的值又是怎么返回的呢?形参不是会销毁吗?

mov         eax,dword ptr [ebp-8]  

这里是将[ebp-8](30)的值保持在eax中。

 

 008813F1  pop         edi  
008813F2  pop         esi  
008813F3  pop         ebx  
008813F4  mov         esp,ebp  
008813F6  pop         ebp  
008813F7  ret  

那这些又是指的是什么?

pop指的是出栈的意思,使得esp不断加加。

其中的mov esp,ebp是将ebp的值给esp,在 pop         ebp  这样esp和ebp维护的空间重新指向main函数。ret是回到函数调用,call指令的下一个地址。

 这样我们就是完成了main函数的创建和Add函数创建和销毁。而main函数的销毁和Add函数的销毁是类似的。

下面我们来回答几个问题,来运用我们今天学习的。

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

首先为函数分配好空间,在为函数初始化,最后在为局部变量在分配好的空间中,分配好地址。

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

因为我们在为函数初始化的时,就是初始化的随机值。

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

将参数存入eax寄存器中,传参的顺序是从右到左。

 4函数调用的结果是如何返回的?

通过call指令记住下一条指令的地址,从而找到main函数,在通过寄存器将返回值带到,main函数中。

 总结

通过对Add函数创建和销毁的理解,我们相信大家对于函数在内存中的分配会有更加深刻的理解。在下面的博客中,博主会给大家带来更多知识,希望大家不要错过噢!

  • 18
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小蜗牛~向前冲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值