浅谈函数栈帧(Stack Frame)

news2024/11/24 8:27:47

💙作者:阿润菜菜

📖专栏:C++


本文目录

什么是栈帧

 在调试中观察

总结


什么是栈帧

那我们先来看看什么是

栈(stack)是限定仅在表尾进行插入或者删除的线性表。栈是一种数据结构,它按照后进先出的原则存储数据。把数据元素存放到栈顶时,叫压栈(push) ,从栈顶删除一个元素,叫出栈(pop)。那什么是栈帧(Stack Frame)呢?

预备知识:

 每一次函数的调用,都会在调用(call stack)上维护一个独立的栈帧空间(stack frame).每个独立的栈帧一般包括:

  • 函数的返回地址和参数
  • 临时变量: 包括函数的非静态局部变量以及编译器自动生成的其他临时变量
  • esp、ebp这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的
  • .ebp(栈底指针):该指针永远指向系统栈最上面一个栈帧的底部
  • esp(栈顶指针):该指针永远指向系统栈最上面一个栈帧的栈顶
  • 栈是从高地址向低地址延伸,一个函数的栈帧用ebp 和 esp 这两个寄存器来划定范围.ebp 指向当前的栈帧的底部,esp 始终指向栈帧的顶部
  • 压栈push :esp上移朝低地址移动;出栈pop:栈顶元素弹出,esp下移高地址

 在调试中观察

 我们使用的环境是VS2013,由于函数栈帧是底层知识,而越高级的编译器越难以抽离出函数栈帧分装的过程,不容易学习和观察。同时在不同的编译器下,函数调用栈帧的创建也是略有差异的,但大体思路都是一样的。

每一个函数独占自己的栈帧空间。当前正在运行的函数的栈帧总是在栈顶。 

如图: 

 在调用main函数的时候,会在栈中开辟一块空间,由ebp和esp共同来维护(在调用哪个函数,ebp和esp就会维护哪块空间)

但main函数是被怎么调用的呢?是被系统内提前建立好的函数栈帧调用的:

通过反汇编可以看到main函数是被_tmainCRTStartup()函数调用的,通过一系列汇编指令调用main函数同时esp和ebp来进行维护:  我们来看下这些汇编指令走的过程

 当执行压栈push时, ebp压到esp顶部,esp上移

执行move时,mov ebp,esp 就是把esp的地址交给edp

此时ebp和esp指向了同一个地址

 下一步是sub  esp,0E4h 就是把esp减去0E4h使esp上移。也就是为main函数开辟了空间

下面就是三个push:分别压进了ebx,esi和edi三个值(具体是什么值,无需关心,后面会自动弹出)

 接下来lea (load effective address)  就是为edi加载有效地址 [ebp - 0E4h]

通过下面mov和rep stos三个命令,我们把ebx到ebp之间的栈空间初始化为eax里的内容 

此时main函数的栈帧空间已开辟好,开始执行真正的有内容的代码:

32位中,word是两个字节,dword(double word)四字节

mov 把0Ah(也就是10)放到ebp-8的位置上,而ebp-8实际上就是为int a开辟一个空间 (局部变量int b =20 ,c = 0 的创建 与变量a 类似)

接下来就是调用Add函数了,我们可以看到是一条mov 指令,把[ebp -14h]值(也就是变量b值)放到eax中;然后就是push,压栈eax(b =20), 下面接着一条mov和push命令,类似压栈将变量a的值压入ecx;

 那么刚刚做的步骤是在为Add函数传参吗?是的。接下来call 命令就是调用,通过调试窗口我们可以清楚的看到a上面就是call指令的下一条指令的地址。这一步是在调用函数的同时把下一条指令的地址压上去,作为函数回归的标记

至此就来到我们的Add函数栈帧,与上面讲的main函数栈帧开辟一样。参数是从右向左压栈的,从上面我们也可以清楚的看到形参不是在Add函数内部创建的,而是回来到我们传参的空间,这也直接证明了形参是实参的临时拷贝这句话 !

 那Add函数是如何带回返回值的呢?可以看到把[ebp-8]里的值也就是int z 放到eax里面,因为这里的eax是寄存器(硬件)啊,寄存器不会因为程序退出就销毁的,相当于拿一个(全局的)寄存器把返回值保存起来,等到执行main函数我们再把它拿出来。

那么函数怎么返回呢?

 在 return z执行后,我们pop弹出,把栈顶的元素取出放到edi里面去,依次pop三次,esp指针就往下走。当我们函数调用完了那这个空间就没必要存在了,所以mov把ebp的地址给esp。

此时esp指到ebp,pop一下把栈顶的元素弹出来,因为里面放的是main函数的栈底指针,把结果弹到ebp里面去就可以瞬间到main栈底了

最后ret这条指令就是栈顶弹出call下一条指令地址然后跳过去,回来后就到了call下一条指令地方。此时add 就把形参的空间还给操作系统,然后把eax的值给[ebp-32]空间就是变量int c的空间。

觉得配合图示很难理解,大家可以结合实操快速掌握函数栈帧的创建和销毁过程

总结

在函数调用的过程中,有函数的调用者(caller)和被调用的函数(callee). 调用者需要知道被调用者函数返回值; 被调用者需要知道传入的参数和返回的地址

函数调用:

  • 参数入栈: 将参数按照调用约定(C语言是从右向左)依次压入系统栈中
  • 返回地址入栈: 将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行
  • 代码跳转: 处理器将代码区跳转到被调用函数的入口处;
  • 栈帧调整:
    1.将调用者的ebp压栈处理,保存指向栈底的ebp的地址(方便函数返回之后的现场恢复),此时esp指向新的栈顶位置; push ebp
    2.将当前栈帧切换到新栈帧(将eps值装入ebp,更新栈帧底部), 这时ebp指向栈顶,而此时栈顶就是old ebp mov ebp, esp
    3.给新栈帧分配空间 sub esp, XXX

函数返回: 

  • 保存被调用函数的返回值到 eax 寄存器中 mov eax, xxx
  • 恢复 esp 同时回收局部变量空间 mov ebp, esp
  • 将上一个栈帧底部位置恢复到 ebp pop ebp
  • 弹出当前栈顶元素,从栈中取到返回地址,并跳转到该位置 ret

内容参考:系统栈的工作原理


 本文完。如有建议或问题欢迎评论区讨论

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C进阶:字符函数和内存函数

字符串函数和内存函数字符函数和内存函数字符函数求字符串长度strlen长度不受限制的字符串函数strcpystrcatstrcmp长度受限制的字符串函数strncpystrncatstrncmp字符串查找strstrstrtok错误信息报告strerror字符函数:内存函数memcpymemmovememcmpmemset库函数的模拟…

2023年网络安全比赛--跨站脚本攻击中职组(超详细)

一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 1.访问服务器网站目录1,根据页面信息完成条件,将获取到弹框信息作为flag提交; 2.访问服务器网站目录2,根据页面信息完成条件,将获取到弹框信息作为flag提交; 3.访问服务器网站目录…

javaWeb jsp

概念&#xff1a; Java Server Pages&#xff0c;Java服务端页面。 其中既可以定义 HTML、JS、CSS等静态内容&#xff0c;还可以定义 Java代码的动态内容 JSP HTML Java。最终解析为一个servlet输出给前端。 jsp实践 <dependency> <groupId>javax.servlet…

ASP.NET Core 3.1系列(24)——依赖注入框架之Autofac

1、前言 前面的博客已经介绍过ASP.NET Core中内置IoC容器的使用方法。对于规模较小的项目来说&#xff0c;内置容器完全够用。但在实际开发业务中&#xff0c;一般更推荐开发者使用Autofac作为系统的IoC容器。相较于微软提供的内置容器&#xff0c;Autofac无论是在功能性还是灵…

Python Socket联机自动匹配双人五子棋(含登录注册系统与界面,数据库连接,可作结课作业,可用于学习)

1、前言 首先&#xff0c;关于源码的获取&#xff0c;本人提供了三种方式&#xff1a; 直接从文章里面CtrlC&#xff0c;CtrlV&#xff0c;然后按照我已给的文件结构搞一下即可&#xff1b;通过积分下载上传到CSDN的资源&#xff1b;点开本人的主页&#xff0c;点击“查看详细…

C语言-字符串+内存函数介绍与模拟实现(10)

目录 思维导图&#xff1a; 字符串与内存函数 求字符串长度 strlen 长度不受限制的字符串函数 strcpy strcat strcmp 长度受限制的字符串函数介绍 strncpy strncat strncmp 字符串查找 strstr strtok 错误信息报告 strerror perror 字符操作 内存操作函数 …

Linux之select、poll、epoll讲解

文章目录1 select、poll、epoll1.1 引言1.2 IO和Linux内核发展1.2.1 整体概述1.2.2 阻塞IO1.2.3 非阻塞IO1.2.4 select1.2.5 共享空间1.2.6 零拷贝1.3 select1.3.1 简介1.3.2 select缺点1.4 poll介绍1.4.1 与select差别1.4.2 poll缺点1.5 epoll1.5.1 epoll相关函数1.5.2 epoll优…

详解floor函数、ceil函数和round函数

1.floor函数 功能&#xff1a;把一个小数向下取整 即就是如果数是2.2 &#xff0c;那向下取整的结果就为2.000000 原型&#xff1a;double floor(doube x); 参数解释&#xff1a; x:是需要计算的数 返回值&#xff1a; 成功&#xff1a;返回一个double类型的数&#xff0c;此数…

6-星际密码

题目 星际战争开展了100年之后&#xff0c;NowCoder终于破译了外星人的密码&#xff01;他们的密码是一串整数&#xff0c;通过一张表里的信息映射成最终4位密码。表的规则是&#xff1a;n对应的值是矩阵X的n次方的左上角&#xff0c;如果这个数不足4位则用0填充&#xff0c;如…

C语言-自定义类型-结构体(11.1)

目录 思维导图&#xff1a; 1.结构体类型的基础知识 1.1结构体的声明 1.2特殊的声明 2.结构的自引用 3.结构体变量的定义和初始化 4.结构体内存对齐 4.1如何计算 4.2如何修改内对齐数 5.结构体传参 写在最后&#xff1a; 思维导图&#xff1a; 1.结构体类型的基础知…

Leetcode:98. 验证二叉搜索树(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 递归&#xff1a; 原理思路&#xff1a; 迭代&#xff08;中序&#xff09;&#xff1a; 思路原理&#xff1a; 问题描述&#xff1a; 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。…

【目标检测】基于yolov6的钢筋检测和计数(附代码和数据集)

写在前面: 首先感谢兄弟们的订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。 Hello,大家好,我是augustqi。 今天给大家分享的目标检测项目是:基于yolov6的钢筋检测和计数实战项目(附代码和数据集…

如何成功发送一个Target 846 EDI报文?

Target塔吉特公司是仅次于沃尔玛的第二大零售百货集团&#xff0c;为客户提供当今时尚前沿的零售服务&#xff0c;物美价廉。而EDI&#xff08;电子数据交换&#xff09;是Target与供应商进行业务往来时要求使用的数据交换方式&#xff0c;具有安全可靠、高效和降低人工成本等优…

磨金石教育摄影技能干货分享|有哪些让你难以忘怀的人文摄影照片

在摄影分类中&#xff0c;人文摄影往往没有明确的释义。它既有纪实摄影的真实&#xff0c;又有艺术摄影的深奥。实际上&#xff0c;人文摄影可以说是二者的结合&#xff0c;在创意和表达上更倾向于艺术性&#xff0c;在画面上更有真实感。1 大雨滂沱这张肖像照极具张力&#xf…

智能家居给我们带来了什么?华秋携手信威安防传感器助力提升家居安全性

智能家居的出现&#xff0c;极大地方便了人们的生活&#xff0c;为生活提供便利舒适的体验&#xff1b;如同洗衣机与洗碗机解放了我们双手一样的道理&#xff0c;智能家居是在生活方方面面为我们了提供最大化的便利可能性。 那么&#xff0c;智能家居是如何为我们生活提供便利…

Jmeter@测试场景

目录 性能测试Jmeter&#xff0c;常用的场景 场景一&#xff1a;Thread Group 场景二、jpgc - Stepping Thread Group 场景三、jpgc - Ultimate Thread Group 场景一&#xff1a;Thread Group 参数配置-线程属性Thread Properties&#xff1a; 1.线程数(Number of Threads)…

并查集的使用

目录 一.介绍 二.并查集的实现 三路径压缩 四.相关题型 4.1省份数量 一.介绍 什么是并查集&#xff1f; 将n个不同的元素划分成一些不相交的集合。开始时&#xff0c;每个元素自成一个 单元素集合&#xff0c;然后按一定的规律将归于同一组元素的集合合并。在这个过程中要…

十五、Express 中使用JWT进行登录验证

cookie 篇 : Node.js 中 cookie的验证登录 | session 篇 : Node.js 中 session验证登录 在前面讲过了两种验证登录的方式&#xff0c;其一是cookie&#xff0c;其二是session&#xff1b;那么在讲JWT之前先来简单的回顾这两种方式区别&#xff1b;cookie和sessi…

成功解决:npm 版本不支持node.js。【 npm v9.1.2 does not support Node.js v16.6.0.】

文章目录1、出现的问题2、查看自己的npm和node版本3、解决方法3.1 寻找对应的版本3.2 升级npm版本4、再次运行项目&#xff0c;成功5、如果上述方法失败&#xff0c;请看这里早起更新代码后&#xff0c;跑前端项目。结果噶了、跑不起来了&#xff1b;不慌&#xff0c;看看日志报…

JiBX 的实操

JiBX 的实操介绍基本概念BECL 介绍JiBX 基础概念开发jar 依赖BECLJiBXPOJO 实体类OrderAddressCustomerShippingOrderFactory 工厂类使用ant 来生成XML和POJO对象的绑定关系idea 使用antant 脚本 build.xmlant 添加 build.xmlbinding.xml报错问题解决测试TestOrder测试结果 如图…