前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C语言】函数栈帧的创建和销毁(逐步分析)

【C语言】函数栈帧的创建和销毁(逐步分析)

作者头像
用户11290673
发布2024-09-25 11:44:29
720
发布2024-09-25 11:44:29
举报
文章被收录于专栏:学习

什么是函数栈帧

我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。 那函数是如何调用的?函数的返回值又是如何返回的?函数参数是如何传递的?这些问题都和函数栈帧有关系。

函数栈帧(stack frame)就是函数调用过程中程序的调用栈(call stack)所开辟的空间,这些空间是用来存放: 1. 函数参数和函数返回值 2. 临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量) 3. 保存上下文信息(包括在函数调用前后需要保持不变的寄存器)。

在不同的编译器下,函数调用的过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。

每一个函数调用都要在栈区穿件一个空间

寄存器

寄存器是 CPU 内部用来存放数据的一些小型 存储区域 ,用来暂时存放参与运算的数据和运算结果。 其实寄存器就是一种常用的 时序逻辑电路 ,但这种时序逻辑电路只包含存储电路。

有:eax   ebx    ecx     edx     ebp     esp

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

举例说明

我们可以深入探究下函数调用在内存空间中到底是怎么运转的

我们可以以下面代码为例来分析

代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS 1
#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\n", c);
	return 0;
}

首先我们先建立main函数的栈帧空间,但我们思考一下,main函数是不是也有可能被其他函数调用那。

我们可以看出在vs2013中mainCRTStartup调用_tmainCRTStartup,而_tmainCRTStartup调用main,可见main函数也是被调用的。

首先,创建一个_tmainCRTStartup函数栈帧,我们假设栈区下面为高地址,上面为地地址。


esp为栈顶指针,ebp为栈底指针


这样我们就可以进入,我们通过汇编代码可以看出第一步为push。

push为压栈操作,push的目标是ebp,所以压栈ebp,压完元素之后,esp移动到新的栈顶。压栈:给栈顶放一个元素进去 。出栈:从栈顶删除一个元素。


下一步是move,将ebp移动到esp

下一步是sub,sub是减的意思,意思是将esp减去0E4h(16进制数字),相当于往低地址方向移动

0E4h地址,此刻esp与ebp围成的紫色部分就是main函数的栈帧


然后是三个push分别将ebx esi edi从栈顶压入,最终esp移动到edi的上方


从lea到rep,这几步总的来说是将main函数栈帧里面都初始化“ccccccccccc” 


 以上就是main函数栈帧创建,接下来就是把值放进去,int a=10,dword是双字节的意思,将a的值放在ebp-8这个空间里

接下来就把b, c也像a一样分别放入对应的位置 


接下来就是传参,将ebp-14h也就是b的空间放入eax寄存器里面,再push一下放入栈顶


再传a,a也一样的道理


接下来就是call指令,call存放的是下一个指令的地址,方便函数返回时直接跳到下一指令


这下算是进入Add函数了创建Add函数栈帧与那main一样 先push ebp将main函数栈底指针地址通过这个元素储存起来方便返回时能找到main函数栈底指针


再move,sub将esp和ebp定义新的位置,再push三个元素ebx,esi,edi,最后再将Add函数栈帧初始化“CCCCCCCCC”

接着给z创建空间,ebp-8的位置

然后就是将传过去的b和a加起来储存在eax寄存器里面


接着将eax里面的值移动到z空间里(ebp-8),此时z空间的值是30,再将这个值放入eax寄存器中,这一步防止函数栈帧销毁时数据流失,所以将值保存在eax中


调用完就开始返回了,pop意思是跳出 ,把这三个元素先跳出


再将esp返回到ebp的位置


此刻esp指向的是我们先前放进的ebp在main函数底栈时的地址,把当时ebp在main函数底栈位置读取用pop,ebp又指向回了main函数的栈底,而esp继续停留这个位置


接着是ret指令,意思是返回到main函数,返回到call指令,而call指令储存的是下一个指令的地址,所以直接返回main函数call指令下一个指令也就是a传参的空间。此刻esp指向的就是a传参的空间。


 然后esp+8,跳出俩传参的空间


再三个pop,将edi,esi,ebx跳出去,到达main函数的空间。


最后将承载着z的值也就是两数和的值的寄存器eax,将值付给ebp-20h也就是c的地址

 此时c就为30了

 结论

局部变量是怎么创建的

创建好函数栈帧后,我们初始化一部分函数空间,而局部变量就在这个空间里分配一个空间,从而创建了局部变量

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

因为随机值是在我们创建函数栈帧时放进去的,函数空间里都是随机值,所以一定要初始化。

函数是怎么传参的,函数传参的顺序是什么

我们通过push将两个实参压栈,从而栈顶有了两个独立空间,将两个值放进去,创建好调用的函数栈帧后,通过指针的偏移量,实现传参。传参顺序从从右向左

形参和实参是什么关系

形参是实参的临时拷贝

函数调用结束后怎么返回的

我们通过push将当时edp在主函数栈底的地址压栈到一个空间,当我们返回指向这个空间是就能读取到主函数栈底的位置,再读取通过call指令存放下一个指令的地址,就直接返回主函数的栈帧里,返回值是通过寄存器存储,保护数据在调用的函数栈帧销毁时不丢失,再通过寄存器将值放入对应的主函数空间

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是函数栈帧
  • 寄存器
  • 举例说明
  •  结论
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档