【进阶】--函数栈帧的创建和销毁详解
目录
一.函数栈帧的概念
二.理解函数栈帧能让我们解决什么问题
三.相关寄存器和汇编指令知识点补充
四.函数栈帧的创建和销毁
4.1.调用堆栈
4.2.函数栈帧的创建
4.3 函数栈帧的销毁
一.函数栈帧的概念
--在C语言中,函数栈帧是指在函数调用过程中,在内存栈中为该函数分配的一块空间,用于存储函数的局部变量,参数,返回地址等信息。
栈帧的结构:
- 参数区:用于存放调用函数时传递给被调用函数的参数。
- 返回地址:记录函数调用结束后要返回的指令地址,以便函数执行完毕后能正确回到调用点继续执行。
- 局部变量区:存储函数内部定义的局部变量。
- ebp和esp相关区域:ebp指向当前栈帧的底部,esp指向当前栈帧的顶部,通过这两个指针来维护函数栈帧。
二.理解函数栈帧能让我们解决什么问题
--在前期的学习中,我们可能会产生很多困惑
比如:
- 局部变量是怎么创建的
- 为什么局部变量的值是随机值
- 函数是怎么传参的?传参的顺序是怎样的?
- 形参和实参是什么关系?
- 函数调用是怎么做的?
- 函数调用结束后是怎么返回的?
当我们理解函数栈帧的创建和销毁后,我们就可以更好的去解决这些问题,如同修练自己的内功,也方便在后期能搞懂更多的知识。
三.相关寄存器和汇编指令知识点补充
相关寄存器:
- eax:通用寄存器,保留临时数据,常用于函数返回值
- ebx:通用寄存器,保留临时数据
- eip:指令寄存器,用于存储下一条要执行的指令的地址
- ebp:栈底寄存器
- esp:栈顶寄存器
相关汇编指令:
- push:将操作数压入栈中,栈顶指针esp也会相应调整
- pop:从栈中弹出数据到指定的位置,栈顶指针esp也会相应调整
- mov:数据传送指令,用于在寄存器之间,寄存器与内存之间传送数据
- add:加法指令,用于将两个操作数相加,结果存放于指定的寄存器中
- sub:减法指令,用于将两个操作数相减,结果存放于指定的寄存器中
- call:过程调用,压入返回地址或转入调用函数
- lea:加载有效地址指令,将操作数的地址加载到指定的寄存器中
- ret:返回地址指令,回到调用位置
四.函数栈帧的创建和销毁
4.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;
}
这段代码我们在vs2022上调试的话,调试进入add函数后,我们就可以观察到函数的调用堆栈(右击勾选,显示外部代码) ,如下图所示
函数调用堆栈是可以反馈函数调用逻辑的,我们可以清晰的观察到,是由invoke_main函数来调用main函数的 ,在此之间的我们就不过多的去考虑了,我们接下来直接从main函数的栈帧创建开始。
4.2.函数栈帧的创建
--当函数每次被调用时,系统都会在栈上为该函数分配一块栈帧空间。首先将调用函数的相关信息,如参数,返回地址等压入栈中,然后调整ebp和esp,为局部变量分配空间
我们先将main函数转到反汇编--调试到main函数第一行时,右键鼠标转到反汇编,反汇编代码如下
int main()
{
//函数栈帧的创建
005518D0 push ebp
005518D1 mov ebp,esp
005518D3 sub esp,0E4h
005518D9 push ebx
005518DA push esi
005518DB push edi
005518DC lea edi,[ebp-24h]
005518DF mov ecx,9
005518E4 mov eax,0CCCCCCCCh
005518E9 rep stos dword ptr es:[edi]
005518EB mov ecx,55C008h
005518F0 call 0055132F
005518F5 nop
//main函数中的主要代码int a = 10;
005518F6 mov dword ptr [ebp-8],0Ah int b = 20;
005518FD mov dword ptr [ebp-14h],14h int c = 0;
00551904 mov dword ptr [ebp-20h],0 c = Add(a, b);
0055190B mov eax,dword ptr [ebp-14h]
0055190E push eax
0055190F mov ecx,dword ptr [ebp-8]
00551912 push ecx
00551913 call 005510B9
00551918 add esp,8
0055191B mov dword ptr [ebp-20h],eax
------------------------------------------------------------ printf("%d\n", c);
0055191E mov eax,dword ptr [ebp-20h]
00551921 push eax
00551922 push 557B30h
00551927 call 005510D7
0055192C add esp,8 return 0;
0055192F xor eax,eax
}
我们可以将上面main函数的函数栈帧创建过程的主要部分单独拆解出来看看,代码如下
005518D0 push ebp
//把ebp寄存器中的值进行压栈,到了esp-4的位置,此时的ebp中存放的是invoke_main函数栈帧的ebp
005518D1 mov ebp,esp
//将esp的值存放到ebp中,相当于ebp来到了invoke_main函数栈帧的esp位置,产生了main函数的ebp
005518D3 sub esp,0E4h
//将esp中的地址减去一个16进制数字0E4h,esp向上移动,产生了新的esp,也就是main函数的esp
//结合上面产生的ebp之后,ebp和esp之间就维护了一块为main函数开辟的栈帧空间
005518D9 push ebx
//将寄存器ebx中的值压栈,esp-4,esp向上移动
005518DA push esi
//将寄存器epi中的值压栈,esp-4,esp继续向上移动
005518DB push edi
//将寄存器edi中的值压栈,esp-4,esp接着向上移动
005518DC lea edi,[ebp-24h]
005518DF mov ecx,9
005518E4 mov eax,0CCCCCCCCh
005518E9 rep stos dword ptr es:[edi]
//以上这四串代码是在初始化main函数的栈帧空间
//1.先将ebp-24h的地址加载到edi中
//2.将9放入ecx中
//3.将0xCCCCCCCC放入eax中
//4.将从ebp-24h到ebp之间ecx个4个字节的数字初始化为0xCCCCCCCC
接下来再来分析main函数中的主要代码
int a = 10;
005518F6 mov dword ptr [ebp-8],0Ah
//将0Ah存储到ebp-8这个地址中,ebp-8的位置其实就是变量aint b = 20;
005518FD mov dword ptr [ebp-14h],14h
//将14h存储到ebp-14h这个地址中,ebp-14h的位置其实就是变量bint c = 0;
00551904 mov dword ptr [ebp-20h],0
//将0存储到ebp-20h这个地址中,ebp-20h的位置其实就是变量c
以上就是局部变量在其所在函数的栈帧空间中创建和初始化的过程c = Add(a, b);
0055190B mov eax,dword ptr [ebp-14h]
// 先传参b,将ebp-14h位置中b的值存储到eax中
0055190E push eax
//将eax的值压栈,esp-4,向上移动
0055190F mov ecx,dword ptr [ebp-8]
//再传参a,将ebp-8位置中a的值存储到ecx中
00551912 push ecx
//将ecx的值压栈,esp-4,继续向上移动 //跳转调用函数
00551913 call 005510B9
//call指令会将call指令的下一条指令的地址进行压栈操作
//这样做可以让函数调用结束后回到call的下一条指令地址后继续执行
00551918 add esp,8
0055191B mov dword ptr [ebp-20h],eax
call指令会执行函数调用逻辑,这个时候我们会跳转到Add函数中,我们再来观察Add函数的反汇编代码
int Add(int x, int y)
{
00551790 push ebp
//将ebp压栈,到了esp-4 的位置,此时的ebp是main函数中的ebp
00551791 mov ebp,esp
//将esp的值给了ebp,ebp来到了原来main函数esp的位置,形成了Add函数的ebp
00551793 sub esp,0CCh
//将esp-00ch,形成了Add函数的esp,结合前面的ebp,ebp和esp维护了一块为Add函数开辟的栈帧空间
00551799 push ebx
//将ebx压栈,esp-4,向上移动
0055179A push esi
//将esi压栈,esp-4,向上移动
0055179B push edi
//将edi压栈,esp-4,向上移动
0055179C lea edi,[ebp-0Ch]
//将ebp-0ch的地址加载到edi中
0055179F mov ecx,3
//将3放入exc中
005517A4 mov eax,0CCCCCCCCh
//将0CCCCCCCCh放入eax中
005517A9 rep stos dword ptr es:[edi]
//将ebp-0ch到ebp之间ecx个4个字节的数字初始化为0CCCCCCCCh
005517AB mov ecx,55C008h
005517B0 call 0055132F
005517B5 nop int z = 0;
005517B6 mov dword ptr [ebp-8],0
//将0的值给到ebp-8中 z = x + y;
005517BD mov eax,dword ptr [ebp+8]
005517C0 add eax,dword ptr [ebp+0Ch]
005517C3 mov dword ptr [ebp-8],eax return z;
005517C6 mov eax,dword ptr [ebp-8]
}
005517C9 pop edi
005517CA pop esi
005517CB pop ebx
005517CC add esp,0CCh
005517D2 cmp ebp,esp
005517D4 call 00551253
005517D9 mov esp,ebp
005517DB pop ebp
005517DC ret
代码执行到Add函数的时候就要开始创建Add函数的栈帧空间了,与前面main函数的栈帧空间创建过程差不多,这里就不详细讲述了,主要是计算求和的时候我们通过偏移访问,访问到了函数调用前压栈进去的参数,这就是形参访问,很好说明了形参就是实参的一份临时拷贝,最后将求出的和通过eax寄存器中带回。
z = x + y;
005517BD mov eax,dword ptr [ebp+8]
// 将ebp+8地址处的数字存储到eax中
005517C0 add eax,dword ptr [ebp+0Ch]
// 将ebp+12地址处的数字加到eax寄存中
005517C3 mov dword ptr [ebp-8],eax
//将eax的结果保存到ebp-8的地址处,其实就是放到z中return z;
005517C6 mov eax,dword ptr [ebp-8]
//将ebp-8地址处的值放在eax中,其实就是把z的值存储到eax寄存器中,通过eax寄存器带回计算的结果
4.3 函数栈帧的销毁
--函数执行完毕后,栈帧被销毁,通过恢复ebp和esp的值,释放栈帧空间,将控制权返回给调用函数,继续执行调用函数中调用之后的代码
当函数调用结束后,前面创建的函数栈帧要销毁,我们来看看Add函数的这部分反汇编代码吧
005517C9 pop edi //在栈顶弹出一个值,存放到edi中,esp+4,向下移
005517CA pop esi //在栈顶弹出一个值,存放到esi中,esp+4,向下移
005517CB pop ebx //在栈顶弹出一个值,存放到ebx中,esp+4,向下移
005517CC add esp,0CCh //esp+0cch,向下移
005517D2 cmp ebp,esp //esp的值给ebp,ebp来到了esp的位置
005517D4 call 00551253
005517D9 mov esp,ebp //再将ebp的值给了esp,回收了Add函数的栈帧空间
005517DB pop ebp
//弹出栈顶的值存放到ebp,栈顶此时的值恰好就是main函数的ebp,esp+4,恢复了main函数栈帧空间的维护
005517DC ret
//ret指令的执行,首先是从栈顶弹出一个值,此时栈顶的值就是call指令下一条指令的地址,此时esp+4,向下移动,然后直接跳转到call指令下一条指令的地址处,继续往下执行
回到了call指令下一条指令的地方
调用完后继续回到main函数后继续执行这两串代码,函数的返回值通过eax带了出来,其中就是x+y的和z,也就是a+b的和c。
结语:本篇文章就到此结束了,对于函数栈帧的创建与销毁,个人能力有限,欢迎大家进行补充,一起交流学习,感谢大家的支持!
相关文章:

【进阶】--函数栈帧的创建和销毁详解
目录 一.函数栈帧的概念 二.理解函数栈帧能让我们解决什么问题 三.相关寄存器和汇编指令知识点补充 四.函数栈帧的创建和销毁 4.1.调用堆栈 4.2.函数栈帧的创建 4.3 函数栈帧的销毁 一.函数栈帧的概念 --在C语言中,函数栈帧是指在函数调用过程中,…...
Hadoop伪分布式模式搭建全攻略:从环境配置到实战测试
引言 作为大数据生态的基石,Hadoop凭借其高可靠性、扩展性成为分布式计算的首选框架。本文将手把手带你完成Hadoop伪分布式模式部署,通过单节点模拟集群环境,为后续学习MapReduce、YARN等核心组件打下基础 目录 引言 Hadoop 发展历史 1.1 起源(2002–2005) 1.1 安装JDK…...
Transformer:颠覆深度学习的架构革命与技术演进
2017年,谷歌团队在论文《Attention Is All You Need》中提出的Transformer架构,彻底改变了人工智能对序列数据的处理范式。它不仅解决了传统循环神经网络(RNN)的长期依赖和并行化难题,更催生了BERT、GPT等划时代模型&a…...

【一】 基本概念与应用领域【数字图像处理】
考纲 文章目录 1 概念2005甄题【名词解释】2008、2012甄题【名词解释】可考题【简答题】可考题【简答题】 2 应用领域【了解】2.1 伽马射线成像【核医学影像】☆2.2 X射线成像2.3 紫外波段成像2.4 可见光和红外波段成像2.5 微波波段成像2.6 无线电波段成像2.7 电子显微镜成像2…...

NU1680低成本、无固件、高集成度无线充电电源接收器
无线充电 电子产品具有无线充电功能使用会更便利,介绍一款低成本、无固件、高集成度无线充电电源接收器NU1680 原理图和BOM可点绑定资源下载,LC部分电容建议X7R。 Load空载切满载测试 (CC Mode) – 尽量保证电子负载没有过冲 – 电子负载不要从0到满…...
JVM 内存分配策略
引言 在 Java 虚拟机(JVM)中,内存分配与垃圾回收是影响程序性能的核心机制。内存分配的高效性直接决定了对象创建的速率,而垃圾回收策略则决定了内存的利用率以及系统的稳定性。为了在复杂多变的应用场景中实现高效的内存管理&am…...

2025MathorCup数学应用挑战赛B题
目录 模型建立与求解 1.问题一的模型建立与求解 1.1 搬迁补偿模型设计 1.2 住户是否搬迁的应对策略与分析 1.3 定量讨论 2.问题二的模型建立与求解 2.1 搬迁方案模型的优化介绍 2.2 模型的评估 2.3 模型结果 3.问题三的模型建立与求解 3.1 拐点存在性分析模型的建立 3.2 模型的…...

组件的基本知识
组件 组件的基本知识 组件概念组成步骤好处全局注册生命周期scoped原理 父子通信步骤子传父 概念 就是将要复用的标签,抽离放在一个独立的vue文件中,以供主vue文件使用 组成 三部分构成 template:HTML 结构 script: JS 逻辑 style: CSS 样…...

Origin绘图操作:图中迷你图绘制
一、背景描述 Origin绘图时,局部数据变化较小,在整体图片中表现为局部曲线重叠在一起,图中y1和y2在x0-2时重叠在一起,需要将局部放大,绘制迷你图 二、实现方法 1.在左边工具栏选择放大镜,按住ctrl在图中…...

数据升降级:医疗数据的“时空穿梭“系统工程(分析与架构篇)
一、核心挑战与量化分析 1. 版本演化困境的深度解析 (1) 格式断层的结构化危机 数据转换黑洞:某医疗信息平台(2021-2023)统计显示: 数据类型CDA R1→R2转换失败率R2→FHIR转换失败率关键失败点诊断记录28.4%19.7%ICD编码版本冲突(18.7%)用药记录15.2%12.3%剂量单位标准化…...

【GESP】C++三级练习 luogu-B2089 数组逆序重存放
GESP三级练习,一维数组练习(C三级大纲中5号知识点,一维数组),难度★☆☆☆☆。 题目题解详见:https://www.coderli.com/gesp-3-luogu-b2089/ 【GESP】C三级练习 luogu-B2089 数组逆序重存放 | OneCoderGE…...

Copilot 上线深度推理智能体 Researcher
近日,微软推出两款首创的工作场景推理智能体:Researcher(研究员)和Analyst(分析师)。它们能以安全合规的方式访问您的工作数据(包括邮件、会议、文件、聊天记录等)及互联网信息&…...
QDrant数据构造及增删改查
QDrant数据构造及增删改查 一、PointStruct介绍 PointStruct 是一种结构体(或数据类),通常用于表示: 向量数据库中的一个数据点(Vector Point),包括它的 ID、向量值(vector embedd…...

日常开发小Tips:后端返回带颜色的字段给前端
一般来说,展示给用户的字体格式,都是由前端控制,展现给用户; 但是当要表示某些字段的数据为异常数据,或者将一些关键信息以不同颜色的形式呈现给用户时,而前端又不好判断,那么就可以由后端来控…...

如何在WordPress网站中设置双重验证,提升安全性
随着互联网的不断进步,网站的安全问题越来越受到重视。尤其对于WordPress这样常用的建站平台,安全性显得尤为重要。尽管WordPress自带一定的安全性,但仅依靠用户名和密码的登录方式仍然存在风险。因此,启用“双重验证”便成为了提…...

Python 虚拟环境管理:venv 与 conda 的选择与配置
文章目录 前言一、虚拟环境的核心价值1.1 依赖冲突的典型场景1.2 隔离机制实现原理 二、venv 与 conda 的架构对比2.1 工具定位差异2.2 性能基准测试(以创建环境 安装 numpy 为例) 三、venv 的配置与最佳实践3.1 基础工作流3.2 多版本 Python 管理 四、…...

8.Android(通过Manifest配置文件传递数据(meta-data))
配置文件 <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.com/tools"><applicationandroid:allowBackup"tr…...

三网通电玩城平台系统结构与源码工程详解(二):Node.js 服务端核心逻辑实现
本篇文章将聚焦服务端游戏逻辑实现,以 Node.js Socket.io 作为主要通信与逻辑处理框架,展开用户登录验证、房间分配、子游戏调度与事件广播机制的剖析,并附上多个核心代码段。 一、服务端文件结构概览 /server/├── index.js …...

02_java的运行机制以及JDKJREJVM基本介绍
1、运行机制 2、JDK&JRE&JVM JDK 基本介绍 (1) JDK 的全称(Java Development Kit Java开发工具包) JDK JRE java的开发工具 [ java, javac, javadoc, javap等 ] (2)JDK是提供给Java开发人员使用的,其…...
istio使用ingress gateway通过header实现对不同服务的路由
要在 Istio 中使用 Ingress Gateway 实现基于 HTTP 头的服务路由,并对请求路径进行前缀去除(例如将 /api/details/xx 重写为 /xx),可以利用 Istio 的 Gateway 和 VirtualService 资源,通过配置路由规则和路径重写来实现…...

[论文阅读]REPLUG: Retrieval-Augmented Black-Box Language Models
REPLUG: Retrieval-Augmented Black-Box Language Models REPLUG: Retrieval-Augmented Black-Box Language Models - ACL Anthology NAACL-HLT 2024 在这项工作中,我们介绍了RePlug(Retrieve and Plug),这是一个新的检索增强型…...

数图信息科技邀您共赴第二十五届中国零售业博览会
数图信息科技邀您共赴第二十五届中国零售业博览会 2025年5月8日至10日,数图信息科技将精彩亮相第二十五届中国零售业博览会(CHINASHOP 2025),与行业伙伴共探零售数字化转型新机遇! 数图展会新品抢先看 数图商品一…...

DeepSeek智能时空数据分析(三):专业级地理数据可视化赏析-《杭州市国土空间总体规划(2021-2035年)》
序言:时空数据分析很有用,但是GIS/时空数据库技术门槛太高 时空数据分析在优化业务运营中至关重要,然而,三大挑战仍制约其发展:技术门槛高,需融合GIS理论、SQL开发与时空数据库等多领域知识;空…...

论文导读 - 基于大规模测量与多任务深度学习的电子鼻系统实现目标识别、浓度预测与状态判断
基于大规模测量与多任务深度学习的电子鼻系统实现目标识别、浓度预测与状态判断 原论文地址:https://www.sciencedirect.com/science/article/abs/pii/S0925400521014830 引用此论文(GB/T 7714-2015): WANG T, ZHANG H, WU Y, …...
conda和bash主环境的清理
好的!要管理和清理 Conda(或 Bash)安装的包,可以按照以下步骤进行,避免冗余依赖,节省磁盘空间。 📌 1. 查看已安装的包 先列出当前环境的所有安装包,找出哪些可能需要清理ÿ…...

Webug3.0通关笔记17 中级进阶(第01-05关)
目录 第一关 出来点东西吧 1.打开靶场 2.源码分析 3.源码修正 4.文件包含漏洞渗透 第二关 提交方式是怎样的啊? 1.打开靶场 2.源码分析 3.渗透实战 (1)bp改包法 (2)POST法渗透 第三关 我还是一个注入 1.打开…...
django.db.utils.OperationalError: (1050, “Table ‘你的表名‘ already exists“)
这个错误意味着 Django 尝试执行迁移时,发现数据库中已经有一张叫 你的表名的表了,但这张表不是通过 Django 当前的迁移系统管理的,或者迁移状态和数据库实际状态不一致。 🧠 可能出现这个问题的几种情况: 1.你手动创…...
Maven 依赖范围(Scope)详解
Maven 依赖范围(Scope)详解 Maven 是一个强大的项目管理工具,广泛用于 Java 开发中构建、管理和部署应用程序。在使用 Maven 构建项目时,我们经常需要引入各种第三方库或框架作为项目的依赖项。通过在 pom.xml 文件中的 <depe…...
SpringBoot配置RestTemplate并理解单例模式详解
在日常开发中,RestTemplate 是一个非常常用的工具,用来发起HTTP请求。今天我们通过一个小例子,不仅学习如何在SpringBoot中配置RestTemplate,还会深入理解单例模式在Spring中的实际应用。 1. 示例代码 我们首先来看一个基础的配置…...

React自定义Hook之useMutilpleRef
概要 我们在React开发时候,有时候需要绑定列表中的多个元素,便于后面对列表中单个元素的操作,但是常用的hook函数useRef只能绑定一个DOM元素,本文提供一个可以解决该问题的自定义hook方法,useMutilpleRef。 代码及实…...