C语言函数栈帧的创建和销毁

文章目录

  • 一、寄存器
  • 二、函数栈帧的创建和销毁
    • 1.什么是函数栈帧?
    • 2.案例代码-讲解
    • 3.总结函数栈帧

一、寄存器

寄存器(Register)是中央处理机、主存储器和其他数字设备中某些特定用途的存储单元。寄存器是集成电路中非常重要的一种存储单元;其可用来暂存指令、数据和地址。在计算机领域,寄存器是CPU内部的元件包括通用寄存器、专用寄存器和控制寄存器。寄存器拥有非常高的读写速度,所以在寄存器之间的数据传送非常快。

1. 相关寄存器:

在这里插入图片描述

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

1.什么是函数栈帧?

函数栈帧是指函数被调用时,系统为该函数在栈(Stack)区开辟的一段内存空间。栈区是一种后进先出(Lsat In First Out)的数据结构,后面会学习数据结构的相关知识。

2. 部分汇编指令:

在这里插入图片描述

2.案例代码-讲解

上面介绍了ebp(栈底指针)和esp(栈顶指针),这两个寄存器中存放的是地址,而这两个地址是用来维护函数栈帧的(维护的是正在调用的那个函数)。
压栈(push):给栈顶放一个元素。
出栈(pop):从栈顶删除一个元素。
下面我们通过一个代码来讲解函数栈帧的知识点:首先所有的函数调用都会在栈区开辟空间(函数栈帧),包括main函数。main函数也是被其他函数调用的:在VS2013的编译器中,main函数是被 __tmainCRTStartup函数调用的,而 _tmainCRTStartup函数又是被mainCRTStartup函数调用的。(栈空间的使用是从高地址向低地址使用的

假设现在就正在调用main函数

在这里插入图片描述注意:上面是在VS2013编译器下的函数栈帧维护过程,对应维护栈帧的寄存器是esp和ebp。而在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。如果使用越高级的编译器,那对于函数栈帧过程的封装更加的复杂,观察学习越不容易理解。所以我们使用VS2013来学习函数栈帧的知识。

下面通过在VS2013的环境下讲解函数栈帧的维护过程:
● 局部变量是怎么创建的?
● 为什么局部变量的值是随机值?
● 函数是怎样传参的? 传参的顺序是怎样的?
● 形参和实参是什么关系?
● 函数调用是怎么做的?
● 函数调用在结束后是怎么返回的?

通过下面的一段代码来讲解函数栈帧:

#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;
}

按F10调试起来,当调试完毕后,就可以在调用堆栈里面看到上面讲过的main函数被_tmainCRTStartup函数,_tmainCRTStartup又被mainCRTStartup函数调用:
在这里插入图片描述在按F10调试起来后,单机鼠标右键转到反汇编:
在这里插入图片描述C语言对应的反汇编代码如下:
在这里插入图片描述去掉显示符号名,把[ ]中的符号转化为具体的地址,因为我们要看具体的运行布局:
在这里插入图片描述下面我们就来逐一理解上面的每一行汇编代码:

(1) 首先我们知道在调用main函数之前,main函数要被_tmainCRTStartup函数调用,在调用完_tmainCRTStartup函数后,开始调用main函数,则上面前六行汇编代码意思是:

push ebp:向栈顶压入一个元素ebp,之后esp也会随之往上移动.
mov ebp,esp:把esp的值赋给ebp,则此时ebp就指向了esp指向的对象.
sub esp 0E4h:让esp这个地址减去0E4h,即让esp指针往上移动0E4h这么多个地址(0E4h是一个16进制数,h意思是这个数是16进制表示)。此时esp与ebp之间的栈空间就是为main函数预开辟的栈帧,因为esp与ebp这两个指针就是用来维护正在调用的函数的。
push ebx.
push esi.
push edi:这个三个寄存器也随之压入栈顶,先不管这三个寄存器,先知道它们压入栈就行。相应的esp(栈顶指针)也会往上移动.

走完了上面六行汇编代码,此时在栈上的运行示意图如下:
在这里插入图片描述(2) 紧接着我们来到了后面的四行代码:
在这里插入图片描述
lea edi,[ebp-0E4h]:lea是加载有效数据的意思,这里意思就是把ebp这个指针减去0E4h以后的地址放到edi里去.
mov ecx,39h:把39h赋给ecx.
mov eax,0CCCCCCCCh:把0CCCCCCCCh这个值赋给eax.
rep stos dword ptr es:[edi]:这句汇编代码的意思是把从edi这个位置开始向下的39h个dword空间的内容全部改成0CCCCCCCCh.(word表示两个字节;dword即double word,表示双字,也就是四个字节的意思。39h是16进制数,换算成十进制就是57)

这四行汇编代码的作用其实就是对main函数预开辟的栈帧空间进行初始化,初始化内容为0CCCCCCCCh:
在这里插入图片描述到这里main函数的栈帧就算开辟完成.

(3) 接下来就是局部变量的创建

在这里插入图片描述

mov dword ptr [ebp-8],0Ah:将0Ah这个值放到ebp-8所指向的空间里.
mov dword ptr [ebp-14h],14h:将14h这个值放到ebp-14h所指向的空间里.
mov dword ptr [ebp-20h],0:将0放到ebp-20h所指向的空间里.

注:dword是每次操作四个字节的空间,0Ah、14h及20h分别是10、20与32的16进制表示。现在知道了为什么变量不初始化时,打印出来的值是随机值的原因就是这个。这里就将a、b、c的值放进了main函数的栈帧里:
在这里插入图片描述从内存中可以查看a、b、c存储的情况:
在这里插入图片描述到这里就准备好了a、b、c三个变量

(4) 紧接着就要调用Add函数:(注:由于每次运行代码时分配的空间有所差异,所以上面的汇编指令的地址与下面的略有不同,但调用过程是一样的)
在这里插入图片描述

mov eax,dword ptr [ebp-14h]:把ebp-14h所指向空间的值赋给eax.
push eax:向栈顶压入eax这个元素(的值),esp也随机向上移动.
mov ecx,dword ptr [ebp-8]:把ebp-8所指向空间的值赋给ecx.
push ecx:向栈顶压入ecx这个元素(的值),esp随之向上移动.

在这里插入图片描述

上面这四步其实就是在进行传参,将a和b的值压入栈,等后面调用Add函数的时候就会用到。所以在调用Add函数之前就已经将形参的值传过去了,所以形参就是实参的一份临时拷贝。

call 009E10E1:调用009E10E1这个地址,其实就是开始调用Add函数了(到call这条汇编指令的时候,要按F11调试才能进入Add函数内部)。在执行了call指令后(按F11),会发现call指令的下一条指令的地址被压入栈顶了(相应的esp也会变化):
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述

按一次F11会跳到下面的界面:
在这里插入图片描述

jmp 009E13C0:跳转到009E13C0这个地址处;而Add函数的地址就是009E13C0。再按一次F11就会开始为Add函数分配栈帧:
在这里插入图片描述上面红框部分其实就是在为Add函数预开辟栈帧,和main函数的栈帧开辟是一样的步骤。解释一下7~8行的汇编代码:(勾选显示符号名)
在这里插入图片描述

lea edi,[ebp-0CCh]:把ebp这个指针减去0CCh以后的地址放到edi里去.
mov ecx,33h:把33h赋给ecx.
mov eax,0CCCCCCCCh:把0CCCCCCCCh赋给eax.
rep stos dword ptr es:[edi]:把从edi这个位置开始向下的33h个dword空间的内容全部初始化为0CCCCCCCCh.

在这里插入图片描述紧接着就是在Add的栈帧上为变量z开辟一块空间,并将传过来的形参的值进行相加再存入z中:
在这里插入图片描述
mov dword ptr [ebp-8],0:将0赋给ebp-8所指向的空间里.(z的初始化)
mov eax,dword ptr [ebp+8]:将ebp+8所指向空间里的值赋给eax.
add eax,dword ptr [ebp+0Ch]:将ebp+0Ch所指向空间里的值加给eax.
mov dword ptr [ebp-8],eax:把eax的值存到[ebp-8]所指向的空间里.(把求和的结果存到z变量里)
mov eax,dword ptr [ebp-8]:将ebp-8所指向空间里的值赋给eax。因为当Add函数调用完毕后,局部变量会被销毁,所以让eax这个寄存器存放刚刚求和的结果,以便后面将结果带回去;即eax这个寄存器不会随着Add函数栈帧的销毁而销毁:

在这里插入图片描述

(5) 当Add函数调用完毕后,就要返回main函数的栈帧:
在这里插入图片描述

pop edi:edi弹出栈顶,相应的esp指针会变化
pop esi:esi弹出栈顶,相应的esp指针会变化
pop ebx:ebx弹出栈顶,相应的esp指针会变化
mov esp,ebp:把ebp的值赋给esp;则此时esp就指向了ebp所指向的空间。这里等于就销毁了Add函数的栈帧
pop ebp:⚽把ebp从栈顶弹出来(出栈);注意此时ebp所指向的空间里存放着原来指向main函数栈帧的栈底指针,所以ebp出栈后,ebp就又回到(指向)了main函数栈帧的栈底,所以为什么在调用其他函数之前会把ebp这个栈底指针给压入栈里存起来,就是当函数调用完成后能够回到原来主调(上一级)函数的栈底。当ebp弹出栈顶后,相应的esp(栈顶指针)也会变化⚽

此时我们虽然回到了main函数的栈帧里,但是代码执行到的位置在哪呢?在调用完Add函数后,应该要回到刚才我们执行call指令的下一条指令处继续执行才可以。所以上面的最后一条汇编指令ret就是要回到call指令的下一条指令处继续执行。
🎈🎈由于此时栈顶存放正好就是call指令的下一条指令的地址,所以通过ret指令,就让call指令的下一条指令的地址弹出栈(相应的esp也会向下移动),此时也就跳回到了call指令的下一条指令处继续执行。

在这里插入图片描述

add esp,8:让esp这个地址加上8其实就是相当于把形参x和y还给操作系统了,销毁了x和y。
mov dword ptr [ebp-20h],eax:把eax的值存到ebp-20h所指向的空间里。因为eax的值就是刚刚在Add函数里求和的结果,现在赋给ebp-20h所指向的空间,而ebp-20h所指向的空间就是我们的c变量,至此就将a和b求和的结果放到了c变量里。

在这里插入图片描述对于函数栈帧的理解就到这里!!!

3.总结函数栈帧

🍆🍆🍆需要注意:函数栈帧、局部变量的开辟是在栈区上开辟的,而全局变量、静态变量是在堆上创建的,要注意区分。我们就可以逐一回答上面提出的一些问题:当局部变量不初始化时,内存中存储的就是像CCCCCCCC这样的随机值。函数的传参也是通过压栈的方式将实参的值压入栈里,所以我们使用的形参其实就是实参的一份临时拷贝。传参的顺序对于上面的Add函数可以看到是先将b的值压入栈,然后再将a的值压入栈。函数在调用结束后返回上一层的主调函数在上面的讲解中也可以深刻的体会到。还需要深刻的理解寄存器是集成到我们的CPU上面的,是独立于内存的;所以函数栈帧的销毁并不会影响到这些寄存器。ebp和esp这两个寄存器需要记住:这两个寄存器是用来维护正在调用函数的栈帧的🔥🔥🔥

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/945841.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

我的博客年度之旅:感恩、成长与展望

目录 感恩有你 技能满点 新年新征程 嘿&#xff0c;各位技术大佬、数码潮咖还有屏幕前超爱学习的小伙伴们&#xff01;当新年的钟声即将敲响&#xff0c;我们站在时光的交汇点上&#xff0c;回首过往&#xff0c;满心感慨&#xff1b;展望未来&#xff0c;豪情满怀。过去的这…

【数据库初阶】MySQL数据类型

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; 数据库初阶 &#x1f389;其它专栏&#xff1a; C初阶 | C进阶 | 初阶数据结构 亲爱的小伙伴们&#xff0c;大家好&#xff01;在这篇文章中&#xff0c;我们将深入浅出地为大家讲解 MySQL…

webrtc 源码阅读 make_ref_counted模板函数用法

目录 1. 模板参数解析 1.1 typename T 1.2 typename... Args 1.3 typename std::enable_if::value, T>::type* nullptr 2. scoped_refptr 3. new RefCountedObject(std::forward(args)...); 4. 综合说明 5.在webrtc中的用法 5.1 peerConnectionFactory对象的构建过…

python参数传递不可变对象含可变子对象

当传递不可变对象时。不可变对象里面包含的子对象是可变的。则方法内修改了这个可变对象&#xff0c;源对象也发生了变化。 a (10, 20, [5, 6]) print("a", id(a))def test01(m):print("m", id(m))m[2][0] 888print("修改m后m的值为{}".forma…

qt5.15.2+visual studio2022 免安装版环境配置

1.环境准备 visual studio2022qt5.15.2&#xff08;免安装版本&#xff09; 2.环境配置 2.1 打开首选项 2.2 添加Qt版本 2.3 构建套件手动添加Qt 5.15.2&#xff08;msvc2019_64&#xff09;并配置如下 3.新建项目 问题1&#xff1a;qt creator 没有欢迎界面 解决办法&#…

KOI技术-事件驱动编程(Sping后端)

1 “你日渐平庸&#xff0c;甘于平庸&#xff0c;将继续平庸。”——《以自己喜欢的方式过一生》 2. “总是有人要赢的&#xff0c;那为什么不能是我呢?”——科比布莱恩特 3. “你那么憎恨那些人&#xff0c;和他们斗了那么久&#xff0c;最终却要变得和他们一样&#xff0c;…

华为消费级QLC SSD来了

近日&#xff0c;有关消息显示&#xff0c;华为的消费级SSD产品线&#xff0c;eKitStor Xtreme 200E系列&#xff0c;在韩国一家在线零售商处首次公开销售&#xff0c;引起了业界的广泛关注。 尽管华为已经涉足服务器级别的SSD制造多年&#xff0c;但直到今年6月才正式推出面向…

007-构建工具大进步:Amper Amper Amper!

Amper Amper Amper! 今天天气不好&#xff0c;送孩子上少年宫之后就在茶馆里坐着。突然看到一个帖子&#xff1a;Project configuration with Amper&#xff0c;看得心情大好。 用Kotlin也有个大概几年的时间&#xff0c;开发了几个小工具&#xff0c;感觉很是不错。但是配置…

STM32 高级 物联网通讯之LoRa通讯

目录 LoRa通讯基础知识 常见的3种通讯协议 远距离高速率的传输协议 近距离高速率传输技术 近距离低功耗传输技术 低功耗广域网 采用授权频段技术 非授权频段 LoRa简介 LoRa的特点 远距离 低功耗 安全 标准化 地理定位 移动性 高性能 低成本 LoRa应用 LoRa组…

SAP月结、年结前重点检查事项(后勤与财务模块)

文章目录 一、PP生产模块相关的事务检查二、SD销售模块相关的事务检查:三、MM物料管理模块相关的事务检查四、FICO财务模块相关的事务检查五、年结前若干注意事项【SAP系统PP模块研究】 #SAP #生产订单 #月结 #年结 一、PP生产模块相关的事务检查 1、月末盘点后,生产用料的…

重装操作系统后 Oracle 11g 数据库数据还原

场景描述&#xff1a; 由于SSD系统盘损坏&#xff0c;更换硬盘后重装了操作系统&#xff0c;Oracle数据库之前安装在D盘(另一个硬盘)&#xff0c;更换硬盘多添加一个盘符重装系统后盘符从D变成E&#xff0c;也就是之前的D:/app/... 变成了现在的 E:/app/...&#xff0c;重新安装…

2D图像测量到3D点云之物体三维尺寸测量!!!!

0&#xff0c;引言 本文将从双目采集的2D图像到3D点云进行转化&#xff0c;并进行物体尺寸测量&#xff0c;旨在为读者展示2D图像如何关联3D点云&#xff0c;并进行相关工业应用。 将2D图像转化为3D点云&#xff0c;并进行物体尺寸测量的技术&#xff0c;在工业领域有着广泛的…

python 渗透开发工具之SQLMapApi Server不同IP服务启动方式处理 解决方案SqlMapApiServer外网不能访问的情况

目录 说在前面 什么是 SQLMapAPI 说明 sqlmapApi能干什么 sqlmapApi 服务安装相关 kali-sqlmap存放位置 正常启动sqlmap-api server SqlMapApi-Server 解决外网不能访问情况 说在前面 什么是sqlmap 这个在前面已经说过了&#xff0c;如果这个不知道&#xff0c;就可以…

如何添加使用高德地图资源

‌高德地图瓦片地址包括以下几种类型‌&#xff1a;‌12 ‌矢量底图‌&#xff1a; 地址&#xff1a;https://wprd04.is.autonavi.com/appmaptile?langzh_cn&size1&scale1&style7&x{x}&y{y}&z{z}描述&#xff1a;包含路网和注记的矢量底图。 ‌卫星影…

智能家居体验大变革 博联 AI 方案让智能不再繁琐

1. 全球AI技术发展背景及智能家居市场趋势 人工智能&#xff08;AI&#xff09;技术的飞速发展正在推动全球各行业的数字化转型。国际电信联盟与德勤联合发布《人工智能向善影响》报告指出&#xff0c;全球94%的商界领袖认为&#xff0c;人工智能技术对于其企业在未来5年内的发…

第三代增强经典BADI-增强菜单栏和子屏幕

文章目录 创建经典BADI实施BADI创建屏幕绘制屏幕 定义GUI运行结果程序代码 创建经典BADI 实施BADI 创建屏幕 绘制屏幕 定义GUI 运行结果 程序代码 *&---------------------------------------------------------------------* *& Report ZRP_BADITEST *& *&-…

联通 路由器 创维SK-WR9551X 联通华盛VS010 组mesh 和 锐捷X32 PRO 无缝漫游

前言 联通路由器&#xff1a;联通创维SK-WR9551X&#xff0c;联通华盛VS010组mesh&#xff0c;并与锐捷X32 PRO混合组网&#xff0c;开启无限漫游。 1、mesh ≠ 无缝漫游 mesh是实现路由器快速组网的一种方式&#xff0c;通过mesh组网后可以实现无缝漫游。 mesh组网的设备要…

如何使用大语言模型进行事件抽取与关系抽取

诸神缄默不语-个人CSDN博文目录 文章目录 1. 什么是事件抽取与关系抽取&#xff1f;2. 示例&#xff1a;使用大语言模型进行事件抽取与关系抽取 1. 什么是事件抽取与关系抽取&#xff1f; 事件抽取是指从文本中识别出与某些“事件”相关的信息。这些事件通常包括动作、参与者、…

Mysql COUNT() 函数详解

简介 COUNT()函数定义 COUNT()函数是SQL中常用的 聚合函数 &#xff0c;用于统计满足特定条件的记录数。它可以灵活地应用于各种查询场景&#xff0c;帮助用户快速获取所需的数据统计信息。该函数不仅能够计算所有行的数量&#xff0c;还能针对特定列进行计数&#xff0c;并支…

SD卡恢复数据:快速找回丢失文件!

由于其小尺寸和便携性&#xff0c;SD卡作为外部存储设备在用户中广泛应用。它适用于各种设备&#xff0c;例如数码相机、摄像机、音乐播放器、手机、电视、无人机等。 但是&#xff0c;与其他类型的存储设备一样&#xff0c;SD卡很精致&#xff0c;使用一段时间后可能会出现程…