函数栈帧的创建和销毁

       很喜欢一部动漫,《星游记》,有这样一句话:“能让我们进步的事情,都是由恐惧开始的,把最恐怖的地方,变成最美丽的景色,这才是男人的梦想。”

在学习函数栈帧前,我们不妨问自己这样几个问题!

1.局部变量是怎么创建的?

2.为什么不初始化的局部变量默认值是随机值?

3.函数是怎么传参的?传参的顺序又是如何?

4.形参和实参是什么关系?

5.函数调用是怎么做的?

6.函数调用结束后怎么返回?那返回值呢?

        我演示所使用的编译器是VS2013,低版本的编译器所看到的内容会全面一些,注意:随着编译器的不同,函数栈帧的创建和销毁会略有差异!!!


进入正题前,让我们了解一下待会所用的内容!

图片可以鼠标左键点开右击选择放大哦!!!

一:注意:函数栈帧顾名思义是在栈区操作的,栈区的特点是向上开辟内存(下面是高地址,上面是低地址)。

二:寄存器的了解

1. eax、ebx、ecx、edx

2.ebp、esp(用来存放地址)

这里着重理解第2类:提到函数栈帧就离不开esp、ebp 俩个寄存器,二者地址之间的内存空间就是用来维护函数栈帧的(其中esp寄存器叫做栈顶指针,ebp寄存器叫做栈底指针),每调用一个函数就要在栈区创建一个栈帧,说白了就是给调用的函数在栈区开辟一块内存空间罢了。

3.在汇编中会看到俩个常见指令:push表示的是压栈:从栈顶压入一个元素。pop表示的是出栈:从栈顶删除一个元素。对应的栈顶指针也会做出相应的偏移。

进入正题!

上面二-2中提到,每调用一个函数就要在栈区开辟一块内存空间,那main()函数是由谁来调用的呢!请看下图

图1

上图很好的解释了主函数main()是被谁调用而来的,注意:待会画栈区图的时候我会直接从__tmainCRTStartup开始。

本次我们的测试案例代码如下

#include<stdio.h>

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

int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	c = add(a, b);
	printf("%d", c);
	return 0;
}

在调试界面中将上述代码右击鼠标转到反汇编,接着继续点击右键注意显示符号名不要勾选这样就可以看到十六进制的数值,方便我们接下来的走读(F11)。

int add(int x, int y)
{
004513C0  push        ebp  
004513C1  mov         ebp,esp  
004513C3  sub         esp,0CCh  
004513C9  push        ebx  
004513CA  push        esi  
004513CB  push        edi  

004513CC  lea         edi,[ebp+FFFFFF34h]  
004513D2  mov         ecx,33h  
004513D7  mov         eax,0CCCCCCCCh  
004513DC  rep stos    dword ptr es:[edi]

//-----------上面的指令是为函数开辟函数栈帧-------------
  
	int z = x + y;
004513DE  mov         eax,dword ptr [ebp+8]  
004513E1  add         eax,dword ptr [ebp+0Ch]  
004513E4  mov         dword ptr [ebp-8],eax  
	return z;
004513E7  mov         eax,dword ptr [ebp-8]  
}
004513EA  pop         edi  
004513EB  pop         esi  
004513EC  pop         ebx  
004513ED  mov         esp,ebp  
004513EF  pop         ebp  
004513F0  ret  

//---------------------------------------------------

int main()
{
00451400  push        ebp  
00451401  mov         ebp,esp  
00451403  sub         esp,0E4h  
00451409  push        ebx  
0045140A  push        esi  
0045140B  push        edi  

0045140C  lea         edi,[ebp+FFFFFF1Ch]  
00451412  mov         ecx,39h  
00451417  mov         eax,0CCCCCCCCh  
0045141C  rep stos    dword ptr es:[edi] 

//-----------上面的指令是为函数开辟函数栈帧-------------

	int a = 10;
0045141E  mov         dword ptr [ebp-8],0Ah  
	int b = 20;
00451425  mov         dword ptr [ebp-14h],14h  
	int c = 0;
0045142C  mov         dword ptr [ebp-20h],0  
	c = add(a, b);
00451433  mov         eax,dword ptr [ebp-14h]  
00451436  push        eax  
00451437  mov         ecx,dword ptr [ebp-8]  
0045143A  push        ecx  
0045143B  call        004511DB  //这里的call指令是重点
00451440  add         esp,8  
00451443  mov         dword ptr [ebp-20h],eax  

下面将展示以上代码在栈区创建和销毁的每一步细节,大家跟着我的思路来哈~!

图2

       在图2基础上执行了3条汇编指令后的栈区图。执行指令后esp和edp的地址变化在图里都有展示!

图3

       在图3的基础上又执行了7条汇编指令后的栈区图。

图4

       这里值得注意的是,随着汇编指令的执行main函数的函数栈帧进行了初始化,内容是0cccccccc,这里给不初始化局部变量的值为随机值(烫烫烫)埋下了伏笔,欲知后事如何!请见图5哈~

图5

        图5中随着汇编指令的进行我们也在main函数的函数栈帧中创建了a,b,c三个变量并进行初始化(养成好的变成习惯),如果我们不进行初始化那这三个变量的值都是随机值0cccccccc。这里插入一个例子,如果运行程序就会打印烫烫烫(第一次见到以为电脑发烫了哈哈哈哈哈~!)

#include<stdio.h>

int main()
{
    char arr[] = {'a','b','c'};
    printf("%s",arr);
    return 0;
}
图6

    继续往下走!重点来了!

图7

图7是call指令的详解,紧接着我们进入add函数的汇编代码,由于add函数和main函数开辟栈帧的方式是类似的,所以add函数的函数栈帧将用图8一步到位。

图8
​​​

下面接着在add的函数栈帧中进行变量的创建(有意思的终于来了!)

图9

图九内容很丰富,就不做解释了!

图10

这里再次强调图10中的ret指令,这里解释我分为俩个步骤:1.返回到call指令的下一条指令的地址处,并且pop掉esp所指向的位置(正是图中紫色的内存框被pop)还又图11就完结了,加油!!!

图11

最后展示一下函数栈帧销毁后的内存!

 函数栈帧销毁后,虽然回到最初的自己,但是有了属于你的亮点!!!

不仅一番彻骨寒,怎得梅花扑鼻香。

函数栈帧的创建和销毁完结。

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值