Add函数带你了解函数栈帧的创建与销毁

在学习C语言函数的过程中,我们学会了如何创建函数,调用函数等。然而函数的创建以及销毁的过程都已被编译器自动执行,我们需要去了解函数栈帧的创建与销毁,帮助我们更好的理解函数在栈区内的基本操作。

1、什么是栈区

讲到栈区,我们不得不提出内存分区的概念,图中表示的是内存中各分区的基本元素。
请添加图片描述
由此,我们可以知道栈区中存放的形参局部变量等,都是函数内部常用的组成元素。

栈区的使用习惯是先使用高地址后使用低地址,由高到低。

2、什么是寄存器

寄存器:是集成到CPU上的独立的存储空间。
作为计算机最小的存储空间,其速度是各个存储设备中最快的,与CPU实时交互的存储设备。
硬盘,内存以及寄存器是完全独立的存储空间。
请添加图片描述

基本寄存器:eax,ebx,ecx,edx,ebp,esp
ebp,esp两个寄存器中存放的栈区内存地址。

每一个函数调用,都要在栈区创建一个空间。
  • ebp(栈底指针)一般存储的是目标函数在栈区内的最高地址。
  • esp(栈顶指针)一般存储的是目标函数在栈区内的最低地址。

这两个寄存去存储的地址是用来维护函数栈帧的,而ebp和esp维护的是正在被调用的函数栈区空间。

寄存器的作用
  • 可以在寄存器内部对数据执行算术及逻辑运算。
  • 可以将值存入寄存器,让其保留并带出。
  • 存于寄存器内的地址可用来指向内存的某个位置,即寻址。

3、函数栈帧关键问题

局部变量如何创建?
首先分配好栈帧空间,初始化该空间,为局部变量分配栈帧中的空间。
为什么没有初始化的局部变量是随机值?
如果不初始化局部变量,该内存地址对应的值就会由操作系统分配随机值。
函数是怎样传参的?传参的顺序是怎样的?
没有进入Add函数时,提前将参数值压入栈中,方便Add函数根据内存地址获取。传参的顺序是调用函数时从右向左传递,因为栈区地址中栈底到栈顶是由高到低,函数在获取参数时是由低到高,所以先压后面的参数,后压前面的参数最合理。
形参和实参的关系?
形参是压栈是开辟的空间,形参与实参值相同,而空间是独立的(临时拷贝)。
函数调用是完成后是怎么返回的?
调用函数之前将call的下一条指令地址压入,然后存入ebp在上一个函数内的地址(记住返回地址)返回值是由寄存器带回的。

4、函数栈帧的创建与销毁


*本次实验均使用十六进制演示

4.1部分指令

 - ret指令-返回栈顶内存地址 
 - push-压栈操作 pop指令-出栈操作
 - add指令-相加操作 
 - call指令-调用函数 
 - mov指令-赋址操作
 - lea(load effective address)指令-加载有效地址 
 - esp寄存器-会随着指令的实行,改变自身的内存地址
 - ecx寄存器-存储的是rep stos操作的移动次数

4.2过程

4.2.1调用main函数

在vs中,首先调用main函数,main函数作为程序的入口,但是它也是被其他函数调用的。

调用过程:main->__tmainCRTStartup->mainCRTStrartup

 - 开始调用函数时,栈区首先压入ebp,此时esp也会随着ebp移动,esp的地址会比之前少4个字节。
 - 将esp的值给ebp,之后开辟新空间,由编译器决定其大小由此改变esp的地址。 
 - push   ebx,esi,edi,每push一次esp的地址会改变(指向顶端)。
 - lea 将ebp地址减去之前开辟的新空间地址大小,放入edi中。
 - mov   ecx,39h  
 - mov   eax,0CCCCCCCCh  dword(doubleword)“双字”-4个字节。
 - rep   stos dword ptr。
     es:[edi]由edi开始向下的39h的空间改为eax的内容(0CCCCCCCCh )

请添加图片描述
- 将main函数预留空间内的值全部初始化为eax的内容 main函数栈帧的开辟到此结束
请添加图片描述

4.2.2Add函数栈帧

4.2.2.1*main函数内创建局部变量
 - mov   dword ptr [ebp-8],0Ah 将0Ah放入ebp-8的位置 ebp-8的位置也就是为a存放的空间。
 - mov   dword ptr [ebp-14h],14h 将14h放入ebp-14h的位置。
   ebp-14h的位置也就是为b存放的空间,这里的14h换算出来是20- dword ptr [ebp-20h],0 存放变量c的值。
4.2.2.2*Add函数栈区创建

请添加图片描述

- 传参操作将b,a重新压栈进入空间内,esp值随之改变。
- 调用函数后,压入call指令的下一条指令的地址。(该命令调试部分在调用Add之前)
- 调用成功后,与main函数栈帧的创建相同,创建一个Add函数栈帧。此时的push操作只是将ebp寄存器借用上来,mov操作才是将ebp真正的挪上来。
- 将上面压入的a,b值以内存地址的方式找到并调用到Add函数中。 
- 将eax的值放入ebp-8(即变量z的内存地址)中,函数参数从右向左传。
- 形参根本不是Add函数内部产生的,而是main函数传参后的空间中。
- 寄存器不会因为程序的推出而销毁,简单理解是函数内部的值存入后,即使函数调用完成仍能保留 将esp的地址赋给ebp,销毁Add函数栈帧。 
- 将之前借的main函数ebp地址弹给现在的ebp,相当于找到了之前ebp的内存地址,此时回到了之前main函数的栈帧中。(即返回操作)
- ret指令返回之前存入的call指令的下一条指令的内存地址。 
- 由于形参x,y已经没用了,所以这里直接跳过(释放该空间),将esp指向高8个字节的地址 main函数的销毁与Add函数的销毁步骤相同。

注意:静态变量不属于栈区,所以其创建与销毁与函数栈帧无关

5、Add函数

#include<stdio.h>

//函数栈帧的测试
int Add(int x, int y)
{
	int z = 0;
	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;
}
基本调试参数

请添加图片描述

关于函数栈帧创建与销毁问题的学习到这里就结束了,并且相关更深层次的逻辑更清晰也会更复杂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

捌音

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

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

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

打赏作者

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

抵扣说明:

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

余额充值