(提升篇)函数栈帧的创建和销毁

news2024/11/27 0:36:49

函数栈帧的创建和销毁

  • 1.前言
  • 2.预备知识
    • 2.1什么是栈帧
    • 2.2什么是栈
    • 2.2常见的寄存器
    • 2.3常见的汇编指令
  • 3.函数栈帧创建和销毁的过程
    • 3.1为main函数创建栈空间
    • 3.2main函数中创建变量
    • 3.3给Add函数传参
    • 3.4调用Add函数
    • 3.5为Add函数创建栈空间
    • 3.6计算
    • 3.7把计算好的值返回

1.前言

  • 本章节我们将系统性的理解前面我们调用函数时的一些疑问:
  1. 局部变量是怎么创建的?
  2. 为什么局部变量的值是随机值?
  3. 函数是怎么传参的?传参的顺序是怎么样的?
  4. 形参和实参是什么关系?
  5. 函数调用是怎么做到?
  6. 函数调用结束怎么返回的?
  • 首先说明以下,编译器越高级越不容易看到函数的创建和销毁的过程函数栈帧的创建和销毁的过程在不同的编译器他的创建和销毁是略有差异的,但是大体逻辑上的一样的具体细节取决于编译器的。
    当前使用的环境是(vs 2022 Debug Win32)版本下的编译器。

2.预备知识

2.1什么是栈帧

  • 从逻辑上讲,栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等。
    实现上有硬件方式和软件方式(有些体系不支持硬件栈)
  • 首先应该明白,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。
  • 注意:EBP指向当前位于系统栈最上边一个栈帧的底部,而不是系统栈的底部。严格说来,“栈帧底部”和“栈底”是不同的概念;ESP所指的栈帧顶部和系统栈的顶部是同一个位置。

2.2什么是栈

  • 栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

  • 总结一点就是,栈:是一种受限制的线性表,他的特点就是,先进的后出,后进的先出,就好比我们生活中把书放进盒子里,最先放进去的如果想要拿出来就必须先把上面的书全部拿走,才能拿到最下面的书。

2.2常见的寄存器

寄存器名称功能
eax累加寄存器,相对于其他寄存器,在运算方面比较常用。
ebx基地址寄存器,在内存寻址时存放基地址。
ecx计数寄存器,用于循环操作,比如重复的字符存储操作,或者数字统计。
edx作为EAX的溢出寄存器,总是被用来放整数除法产生的余数。
esi源变址寄存器,主要用于存放存储单元在段内的偏移量。通常在内存操作指令中作为“源地址指针”使用。
edi目的变址寄存器,主要用于存放存储单元在段内的偏移量。
eip控制寄存器,存储CPU下次所执行的指令地址(存放指令偏移地址)。
esp栈顶指针,堆栈的顶部是地址小的区域,压入堆栈的数据越多,esp也就越来越小。在32位平台上,esp每次减少4字节。栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。是CPU机制决定的,push、pop指令会自动调整esp的值。
ebp基址指针,指栈的栈底指针。基址指针寄存器(extended base pointer),一般与esp配合使用,可以存取某时刻的esp,这个时刻就是进入一个函数内后,CPU会将esp的值赋给ebp,此时就可以通过ebp对栈进行操作,比如获取函数参数,局部变量等。其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

2.3常见的汇编指令

  1. push指令:它首先减少esp的值,再将源操作数复制到栈地址,在32位平台上,esp每次减少4字节。

  2. pop指令:它首先把esp指向的栈元素内容复制到一个操作数中,再增加esp的值。在32位平台上,esp每次增加4字节。

  3. mov指令:用于将一个数据从源地址传送到目标地址,源操作地址的内容不变。

  4. sub指令:减操作指令,从寄存器中减去<shifter_operand>表示的数值,并将结果保存到目标寄存器中。

  5. lea指令:是“load effective address”的缩写,简单的说,lea指令可以用来将一个内存地址直接赋给目的操作数。

  6. rep指令:重复前缀指令,英文缩写 repeat。能够引发其后字符串指令被重复。

  7. stos指令:串存储指令,英文缩写 store string。

  8. call指令:将程序下一条指令的位置的IP压入堆栈中,并转移到调用的子程序。

  9. jmp指令:无条件跳转指令。

  10. add指令:用于将两个运算子相加,并将结果写入第一个运算子。

  11. ret指令:用于终止当前函数的执行,将运行权交还给上层函数。也就是,当前函数的帧将被回收。

3.函数栈帧创建和销毁的过程

首先我们先写一个比较简单的但是步骤比较详细的函数,来分析他的创建和销毁过程。

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

而我们每调用一个函数的时候,都会在栈空间中开辟一块空间。
在这里插入图片描述
而esp和ebp是用来维护函数栈帧的,我当前调用那个函数,他们就维护哪一块栈帧。

  1. 我们先分析以下main函数:
    我们按F10调试,选择调用堆栈:
    在这里插入图片描述
    于是我们就可以看到我们程序中所有调用的函数:
    在这里插入图片描述
    在这里我们可以看到的是,我们的main函数其实也是被别的函数调用的。
    注:这里我们点击调用堆栈的窗口,右击鼠标,打开显示外部代码,不然会看不到函数之间的调用。
    在这里插入图片描述
    由于当前的版本是2022版本的,所以有些细节是很难看到的,只能看到大概的。
    我们就可以总结以下。
    我们的main函数是被__scrt_common_main_seh函数调用的。
    而__scrt_common_main_seh又被__scrt_common_main所调用。
    他们的关系就像是套外一样,一个套一个的样子。
    在这里插入图片描述

3.1为main函数创建栈空间

我们上面知道了,main函数其实也是被别的函数所调用的,所以我们在执行的main函数之前还有一些步骤,也就是为main函数准备过程。
首先我们要调用main函数,首先要调用__scrt_common_main_seh,而要调用__scrt_common_main_seh就要先调用__scrt_common_main函数,所以我一开始esp和ebp为维护的栈空间是__scrt_common_main函数的。

我们首先一下F10之后按打开调用堆栈的步骤打开反汇编。
我们就可以看到我们代码的汇编代码了。
在这里插入图片描述

这里我们为了方便观察到我们代码在内存中的地址的变化,右击鼠标,把显示符号名关闭,这个时候我们看到的就是地址之间的变化了。

首先是
在这里插入图片描述
注:我们的push这个压栈的过程有个动作,就是push后esp的值会变,就是说,我push后esp指向的push这个值地址了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2main函数中创建变量

在这里插入图片描述

在这里插入图片描述
这里就可以解释一下,为什么我们一直都强调我们第一变量的时候一定初始化,如果不初始化的话,我们内存里面放到就是CCCCCCCC这个样的数,这也是为什么我们有时候没有初始化变量,显示屏上会出现烫烫烫烫这个样的字。

3.3给Add函数传参

在这里插入图片描述

在这里插入图片描述

3.4调用Add函数

在这里插入图片描述

在这里插入图片描述
而存放call指令的下一条指令的地址的目的就是,当我们Add调用完后,返回时可以通过这个地址找到,并返回。

3.5为Add函数创建栈空间

在这里插入图片描述

在这里插入图片描述

3.6计算

在这里插入图片描述
在这里插入图片描述
所以我们可以发现,我们的形参根本就不是在Add函数中创建的,而是在Add函数开辟好空间之前就已经把参数传过去了,而且先传过去的是b,也就是说我们传参是从右往左传参的。当创建好Add函数并实现功能的时候,这两个数相加的时候,找参数是回去找我们刚刚调用的时候push压进去的这块空间
这个时候,上图x和y分别是:
在这里插入图片描述

3.7把计算好的值返回

在这里插入图片描述
紧接着就是返回计算好的值,但是我们返回z,z不是会被销毁吗,所以这个时候我们就可以利用寄存器的全局性,所以有这个样一个操作:把[ebp-8]的值放到eax寄存器中。

在这里插入图片描述
上面就式把,Add函数的栈帧空间销毁了。
在这里插入图片描述
在这里插入图片描述
最后还有一步就是main函数栈空间的销毁了,其实和Add函数栈空间销毁是一样的。
在这里插入图片描述
这个就想到做式思考题把,小伙伴们可以自己尝试一下。

现在我们就来总结我们一开始提出的问题:

  1. 局部变量是怎么创建的?
    局部变量的创建首先我们为这个函数创建好栈帧空间,我们在这个栈帧中我们初始化好一部分空间之后,然后给我们给局部变量分配空间
  2. 为什么局部变量的值是随机值
    之所以会出现随机值,是因为我们在为函数创建栈空间的时候我们随机放进去的,所以不初始化的话拿到的就是随机值。
  3. 函数是怎么传参的?传参的顺序是怎么样的?
    当我们要调用某个函数的时候,其实在没调用这个函数的时候我们已经push把参数从右向左开始压栈压进去了,当我们真正进入形参函数的时候,通过指针的偏移量找到形参。
  4. 形参和实参是什么关系?
    形参确实是在压栈的时候开辟的空间,他和实参只是值上是相同的,但是空间是独立的所以形参是实参的一份临时拷贝,改变形参不会影响实参。
  5. 函数调用是怎么做到?
    通过一个call汇编指令,调用函数,同时记住call指令下一条指令的地址,为了我们函数返回的时候,可以通过这个地址,跳转回call指令下一个指令
  6. 函数调用结束怎么返回的?
    我们在调用之前就把调用这个函数的上一个函数的ebp(栈底指针)push压到栈中了,当我们函数调用完后要返回的时候弹出ebp就能找到上一个函数调用的ebp,而当esp顶指针往下走到时候,就会找到我们之前记住call指令的下一条指令的地址,然后跳转到call指令下一条指令。
    而他的返回值则是通过eax寄存器存起来,然后往回带。

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

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

相关文章

【C/C++数据结构与算法】C语言函数栈帧

目录 一、源代码理论分析 二、主函数的创建 三、c语言代码的汇编 注&#xff1a;不同编译器环境的函数栈帧存在一定差异&#xff0c;本文使用VS2019 一、源代码理论分析 源代码&#xff1a; int Add(int x, int y) {int z 0;z x y;return z; }int main() {int a 10;…

Springboot分布式事务

一、先了解什么是本地事务 1. 概念 本地事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器位于同一节点相同数据库上。 又称为传统事务。它是一个操作序列&#xff0c;这些操作要么都执行&#xff0c;要么都不执行&#xff0c;是一个不可分割的工作单位。例…

【LeetCode】HOT 100(16)

题单介绍&#xff1a; 精选 100 道力扣&#xff08;LeetCode&#xff09;上最热门的题目&#xff0c;适合初识算法与数据结构的新手和想要在短时间内高效提升的人&#xff0c;熟练掌握这 100 道题&#xff0c;你就已经具备了在代码世界通行的基本能力。 目录 题单介绍&#…

英特尔发布12Q CMOS量子芯片

光子盒研究院 今天&#xff0c;英特尔公司发布了一种在主流CMOS工艺技术上构建的具有12个量子比特的量子芯片——Tunnel Falls。 Tunnel Falls测试芯片栖息在指尖上 Tunnel Falls量子芯片由12个量子点&#xff08;或单电子晶体管&#xff09;构建&#xff0c;可配置4至12个基于…

嵌入式 QT usb camera库驱动摄像头

目录 前言 ​编辑 1. 交叉编译 ffmpeg 1.1 先拿到ffmpeg的压缩包 1.2 把ffmpeg源码复制到虚拟机&#xff0c;并解压压缩包 1.3 解压之后我们要进入到解压文件中进行编译成库 1.5 make 1.6 make install 2.usbcame库使用 2.1 获取usbcame库并解压 2.2 解压完成后我们…

zabbix-3-agent安装

1.CentOS release 5 1-1.centos5 32位 [rootLV zabbix]# cat /etc/redhat-release CentOS release 5 (Final) [rootLV zabbix]# uname -a Linux LV 2.6.18-53.el5xen #1 SMP Mon Nov 12 03:26:12 EST 2007 i686 i686 i386 GNU/Linux确定了系统centos5 32位rpm方式安装&#…

【深度学习】3-1 神经网络的学习- 学习基础/损失函数

学习基础 神经网络的学习。这里所说的“学习”是指从训练数据中自动获取最优权重参数的过程。 神经网络的特征就是可以从数据中学习。所谓“从数据中学习”&#xff0c;是指可以由数据自动决定权重参数的值。 数据驱动 数据是机器学习的命根子。从数据中寻找答案、从数据中发…

接口测试框架之APIAutoTest框架

目录 前言 框架简介 框架介绍 框架技术栈图 框架组件图 框架执行流程图 测试用例设计 测试报告 结语 前言 APIAutoTest框架是一款基于Python语言和unittest框架的API自动化测试框架。它可以帮助测试人员快速搭建API测试环境&#xff0c;并通过代码自动化实现API的测试…

每年降本100万元+!企业研发管理可以这样做....

随着市场的定制化需求越来越多&#xff0c;非标设计越来越多&#xff1b;订单交付周期要求短&#xff0c;导致设计人员的设计周期进一步压缩&#xff0c;设计准确率的重要性进一步提升&#xff0c;这些都对企业研发管理提出了更严峻的挑战。 本期干货内容分享 装备制造企业研发…

计算机网络基础学习指南

前言 计算机网络基础是研发/运维工程师都需掌握的知识&#xff0c;但往往会被忽略。 今天&#xff0c;我将对计算机网络基础学习进行详细阐述&#xff0c;涵盖 TCP / UDP协议、Http协议、Socket等&#xff0c;希望你们会喜欢。 1、计算机网络体系结构 1.1 简介 定义 计算机…

Linux基础学习之目录结构、ls命令

1、win系统可能有多个盘&#xff0c;那么就会对应多个文件树&#xff0c;但是Linux系统只有一个文件树&#xff0c;对应的也只有一个根目录&#xff0c;叫做 / 。 2、win与Linux表示层级关系的斜杠的方向不同&#xff0c;一个是斜杠&#xff0c;一个是反斜杠&#xff0c;Linux…

Three.js3D可视化介绍,以及本地搭建three.js官网

一、什么是Three.js three.js官网&#xff1a;https://threejs.org/ Three.js是一个基于WebGL的JavaScript 3D图形库&#xff0c;它可以轻松地在浏览器中创建3D场景和动画。同时&#xff0c;它支持外部模型和纹理的导入&#xff0c;让开发者可以更加便捷地创建出震撼的3D场景…

详细的聊一聊如何使用响应式图片,提升网页加载速度

开篇 确保图片在所有屏幕尺寸上都能良好显示是一项困难的任务&#xff0c;因为你需要考虑图片的大小、图片的放置位置、显示图片的比例、用户连接的速度等等众多因素。结果是&#xff0c;大多数开发者只会为所有屏幕尺寸使用同一张图片&#xff0c;并让浏览器调整图片的大小以适…

【SCI征稿】仅1个月22天录用,极速录用见刊,可接受智能交通,自动驾驶汽车,新型智能算法,智能交通系统,安全运输,交通拥堵等领域

智能算法类SCIE&EI ◆ 影响因子&#xff1a;4.0-5.0 ◆ 期刊分区&#xff1a;JCR2区&#xff0c;中科院3区 ◆ 检索情况&#xff1a;SCIE&EI 双检&#xff0c;正刊 ◆ 征稿领域&#xff1a;智能算法在智能交通中的应用&#xff0c;包括自动驾驶汽车、新型智能算法、…

20个ArcGIS Pro 提示和技巧

ArcGIS Pro是最通用的GIS软件在当今的行业中。为了帮助你充分利用ArcGIS Pro&#xff0c;我们整理了一份20个提示和技巧。 这些技巧包括从基本的定制选项到高级地理空间分析和数据可视化技巧。 链接地图视图 在ArcGIS Pro中链接视图有助于同步显示一个项目中的多个地图或场景…

java项目之汉服文化平台网站(ssm+vue)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的汉服文化平台网站。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风歌&…

2017年全国硕士研究生入学统一考试管理类专业学位联考写作试题

2017年1月真题 四、写作&#xff1a;第56~57小题&#xff0c;共65 分。其中论证有效性分析30 分&#xff0c;论说文35分。 56、论证有效性分析&#xff1a; 分析下述论证中存在的缺陷和漏洞&#xff0c;选择若干要点&#xff0c;写一篇600字左右的文章&#xff0c;对论证的有…

GPT提示词系统学习-第二课-使用GPT帮你“填表格”

开篇 前面我们说到了GPT的提示词使用的好不好对你可以得到精准的答案是至关重要的。今天我们来看看,GPT中如何使用提示词来帮助我们填一个表格。 参照样本让GPT制作可填表格 请阅读以下销售邮件。删除任何可用于识别个人身份的信息 (PII),并用相应的占位符替换它。 例如,…

BIO-NIO-AIO案例

BIO-NIO-AIO案例 1、BIO编程 BIO 有的称之为 basic(基本) IO,有的称之为 block(阻塞) IO&#xff0c;主要应用于文件 IO 和网络 IO&#xff0c; 这里不再说文件 IO, 大家对此都非常熟悉&#xff0c;本次课程主要讲解网络 IO。 在 JDK1.4 之前&#xff0c;我们建立网络连接的时…

endnotex9 在word中插入参考文献遇到的问题

添加超链接 在EndNote X9中点击如下按钮 勾选如图所示选项后确认即可 找到插入文献的上标&#xff0c;按住“Ctrl”后鼠标点击即可跳转到对应文献 修改文献添加位置 先使用endnote插入一篇文献&#xff0c;然后到达插入文献的位置&#xff0c;右键点击。 选择切换域代码 将其…