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

news/2024/4/19 2:27:41

函数栈帧的创建和销毁

  • 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.ppmy.cn/news/468958.html

相关文章

arm linux 电源管理,Linux Kernel之ARM 平台下电源管理常用机制来龙去脉浅析

1 User Space通常需要操作到PM的一些AP或tools简介 1. /sbin/init&#xff0c;init program。我们可以通过执行init N进入某种run level。N&#xff1d;0&#xff1a;关机(Embedded Device中常不支持)&#xff0c;N&#xff1d;6 reboot。 2. /sbin/reboot reboot…

linux冗余服务器,linux – 一台服务器,两台APC UPS上的冗余电源:如何触发关机?...

目前,当两个UPS中的一个死亡时,会触发doshutdown事件,并通过apccontrol执行默认脚本. doshutdown脚本忽略了第二台UPS,因为它们没有事件连接,并且正常关机. 为了使doshutdown事件有点连接,apcupsd的两个实例需要一个专门定制的配置文件.差异将驻留在必须从中执行事件脚本的目录…

如何快速为门禁制作一个ups(不断电)

想必很多人点进这篇文章的第一想法是&#xff0c;什么是ups&#xff1f;b话少说我们去百度。 这是啥&#xff1f;害不就是个门禁停电不断电的呗。冲冲冲。 这边鉴于国外创客的思想就是&#xff0c;开局一焊枪&#xff0c;模块全靠嫖&#xff01;我集齐了升压模块&#xff0c;降…

UPS电源如何安装?如何配置计算?故障如何处理?

一个典型的数据中心供电系统&#xff0c;由中压配电、变压器、低压配电、不间断电源、末端配电以及发电机等设备组成。其中&#xff0c;UPS的主要作用&#xff0c;是在市电电源中断、发电机启动之前&#xff0c;确保所带的负载持续供电。 UPS系统是数据中心供电连续性的重要保障…

无盘服务器需要用ups吗,如果为机房服务器选择合适的UPS不间断电源

科华UPS 称为不间断电源&#xff0c;是因为停电的时候&#xff0c;它能快速转换到“逆变”状态&#xff0c;从而不会让在使用中的电脑因为突然停电未来得及存储而失去重要文件。 不是用来当备用电源用的&#xff0c;如果你只是想在停电的时候可以用电&#xff0c;光买逆变器就够…

IDC初次选择UPS电源需要考虑哪些问题及故障检测?

更多专业文档请访问 www.itilzj.com # UPS初选择 # 随着现代社会的不断发展&#xff0c;人们的生活和生产需求不断提高&#xff0c;“电”对于人们来说是不可缺少的东西&#xff0c;由于供电压力过大&#xff0c;或者电流不稳&#xff0c;为了保证不断电&#xff0c;越来越多的…

火电厂中UPS电源的应用

UPS电源也被称之为不间断电源&#xff0c;不间断电源也被称为UPS&#xff0c;其实二者指的是同一种东西。在火电厂及其他很多领域UPS不间断电源的持续供电功能被普遍应用于许多重要设施&#xff0c;UPS不间断电源可以解决很多电力方面的问题&#xff0c;比如&#xff1a;电源断…

计算机学的建模是什么意思,请问建模是什么意思?

偶引用个例子,你看看就明白了!什么叫建模,数学建模等等等! 数学建模竞赛,就是在每年叶子黄的时候(长沙的树叶好像一年到头都是绿的)开始的一项数 学应用题比赛。大家都做过数学应用题吧,不知道现在的教育改革了没有,如果没有大变化, 大家都应该做过,比如说[树上有十只…

初次选择UPS电源需要考虑哪些问题?

# UPS初选择 # 随着现代社会的不断发展&#xff0c;人们的生活和生产需求不断提高&#xff0c;“电”对于人们来说是不可缺少的东西&#xff0c;由于供电压力过大&#xff0c;或者电流不稳&#xff0c;为了保证不断电&#xff0c;越来越多的企业和个人选择了不间断电源&#xf…

Linux常见指令(超详解哦)

Linux常见指令 引言Linux常见指令查指令——man文件管理相关指令lspwdcdtouchmkdirrmdir与rmrmdirrm cpmvfind 文件查看类catmorelesshead 与 tailheadtail使用管道显示某段内容 grep 打包压缩相关指令zip/unziptar 总结 引言 Linux与我们熟悉的Window都是操作系统&#xff0c…

WPy64的Python开发环境中安装pinyin库方法举例和应用

WPy64的Python开发环境中安装拼音库&#xff08;pypinyin&#xff09;方法举例和应用 在Python开发环境中安装拼音库后&#xff0c;我们就可以实现对汉字的注音显示。下面以WPy64为例子&#xff0c;讲解pypinyin库的安装方法。 步骤&#xff1a; 一、找到WPy64所安装的目录中…

Python 入门必备知识

当你开始学习Python编程时&#xff0c;以下是一些入门必备的知识&#xff1a; 1. 变量和数据类型&#xff1a; 了解如何声明变量&#xff0c;并了解Python中的不同数据类型&#xff0c;如整数、浮点数、字符串、列表、元组和字典。 好的&#xff0c;我将为你提供详细的代码说…

【ARMv8/v9 异常模型入门及渐进2 - 系统控制寄存器 SCTRL_ELx 介绍】

文章目录 SCTRL_ELx 介绍背景ARMv8 SCTLR_ELx 介绍ARMv9 SCTLR_ELx 介绍 SCTRL_ELx 介绍背景 由于在做DFD 测试过程中需要测试 EL1 状态下的 self-hosted trace 功能&#xff0c;但是这个测试是在UEFI中做的&#xff0c;在开发验证阶段UEFI默认是运行在EL3 下的&#xff0c;所…

截至目前最强的70亿参数大语言模型:开源可商用的RedPajam 7B完全版发布!

RedPajama模型是TOGETHER发布的一个开源可商用的大模型。2023年6月6日&#xff0c;TOGETHER在官方宣布该模型完成训练&#xff0c;经过测试&#xff0c;该模型目前超过所有7B规模的大模型&#xff0c;比LLaMA-7B和Falcon-7B的效果还要好&#xff01; TOGETHER公司是一家由豪华管…

3. QT环境下使用OPenCV操作图像数据(读取、保存、尺寸调节、色彩变换等)

1. 说明 图像加载完成后,如果需要显示出来,需要使用imshow函数,在QT框架下,可以不使用这个函数。本文的操作都是将读取到的图像数据绘制到一个QLabel控件上即可。 使用opencv自带的显示函数示例: cv::Mat src = cv::imread("D:/LearnQt/opencv/firstTest/images/te…

可用的游戏代练系统源码

源码介绍&#xff1a; 源码已经被某位大佬去除授权&#xff0c;亲测搭建可用&#xff0c;整体没问题&#xff0c;个别地方存在一点bug&#xff0c;以前版本萌代练系统源码2.0已经不能用了&#xff0c;这个最新版本可以用&#xff0c;有能力自己可以进行二开&#xff0c;程序安…

迭代器模式——电视遥控器的实现

迭代器模式 题目 电视机遥控器就是一个迭代器的实例&#xff0c;通过它可以实现对电视机频道集合的遍历操作&#xff0c;现有TCL和创维两种品牌电视机&#xff0c;模拟电视机遥控器的实现。以下给出了一种实现方式的UML类图。 类图 【分析】 图中Television是抽象聚合类&…

android tv播放ftp,乐视TV超级电视FTP功能的详细使用方法步骤

马上就要过年了&#xff0c;亲们是否准备回家和家人欢聚一堂&#xff1f;手机里面存放着自己的生活照&#xff0c;但手机屏幕太小了&#xff0c;如何让家人同时观看手机中的照片和视频呢&#xff1f;这个时候&#xff0c;乐视TV超级电视的FTP功能就发挥作用了&#xff0c;摆脱手…

乐视超级电视无法进入界面的问题及解决办法,安装电视家

近期电视桌面的频繁自动更新出现的bug 恢复出厂设置的方式&#xff1a; 转载自&#xff1a;乐视电视无法开机启动 乐视电视开机的时候启动不了什么原因_伊秀经验 1、乐视电视无法开机启动建议将电视恢复出厂设置尝试下呢&#xff0c;在电视待机状态下&#xff08;通电后打开…

乐视max70老款_这货是电视?超大尺寸乐视TV Max70试玩

【PConline 第一时间】在2013年&#xff0c;我们的“家电之王”电视&#xff0c;除了从1080p到4k从分辨率上发展之外&#xff0c;尺寸也变得越来越大。几年前&#xff0c;一部42寸的平板电视都可以被大家称为大电视&#xff0c;到去年&#xff0c;50寸左右的尺寸已经是人们选购…