【进阶】--函数栈帧的创建和销毁详解

news2025/7/19 0:12:05

目录

一.函数栈帧的概念

二.理解函数栈帧能让我们解决什么问题

三.相关寄存器和汇编指令知识点补充

四.函数栈帧的创建和销毁

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的位置其实就是变量a
	int b = 20;
005518FD  mov         dword ptr [ebp-14h],14h 
//将14h存储到ebp-14h这个地址中,ebp-14h的位置其实就是变量b
	int 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。


结语:本篇文章就到此结束了,对于函数栈帧的创建与销毁,个人能力有限,欢迎大家进行补充,一起交流学习,感谢大家的支持!

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

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

相关文章

【一】 基本概念与应用领域【数字图像处理】

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

NU1680低成本、无固件、高集成度无线充电电源接收器

无线充电 电子产品具有无线充电功能使用会更便利&#xff0c;介绍一款低成本、无固件、高集成度无线充电电源接收器NU1680 原理图和BOM可点绑定资源下载&#xff0c;LC部分电容建议X7R。 Load空载切满载测试 (CC Mode) – 尽量保证电子负载没有过冲 – 电子负载不要从0到满…

2025MathorCup数学应用挑战赛B题

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

组件的基本知识

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

Origin绘图操作:图中迷你图绘制

一、背景描述 Origin绘图时&#xff0c;局部数据变化较小&#xff0c;在整体图片中表现为局部曲线重叠在一起&#xff0c;图中y1和y2在x0-2时重叠在一起&#xff0c;需要将局部放大&#xff0c;绘制迷你图 二、实现方法 1.在左边工具栏选择放大镜&#xff0c;按住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三级练习&#xff0c;一维数组练习&#xff08;C三级大纲中5号知识点&#xff0c;一维数组&#xff09;&#xff0c;难度★☆☆☆☆。 题目题解详见&#xff1a;https://www.coderli.com/gesp-3-luogu-b2089/ 【GESP】C三级练习 luogu-B2089 数组逆序重存放 | OneCoderGE…

Copilot 上线深度推理智能体 Researcher

近日&#xff0c;微软推出两款首创的工作场景推理智能体&#xff1a;Researcher&#xff08;研究员&#xff09;和Analyst&#xff08;分析师&#xff09;。它们能以安全合规的方式访问您的工作数据&#xff08;包括邮件、会议、文件、聊天记录等&#xff09;及互联网信息&…

日常开发小Tips:后端返回带颜色的字段给前端

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

如何在WordPress网站中设置双重验证,提升安全性

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

Python 虚拟环境管理:venv 与 conda 的选择与配置

文章目录 前言一、虚拟环境的核心价值1.1 依赖冲突的典型场景1.2 隔离机制实现原理 二、venv 与 conda 的架构对比2.1 工具定位差异2.2 性能基准测试&#xff08;以创建环境 安装 numpy 为例&#xff09; 三、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 服务端核心逻辑实现

本篇文章将聚焦服务端游戏逻辑实现&#xff0c;以 Node.js Socket.io 作为主要通信与逻辑处理框架&#xff0c;展开用户登录验证、房间分配、子游戏调度与事件广播机制的剖析&#xff0c;并附上多个核心代码段。 一、服务端文件结构概览 /server/├── index.js …

02_java的运行机制以及JDKJREJVM基本介绍

1、运行机制 2、JDK&JRE&JVM JDK 基本介绍 &#xff08;1&#xff09; JDK 的全称(Java Development Kit Java开发工具包) JDK JRE java的开发工具 [ java, javac, javadoc, javap等 ] &#xff08;2&#xff09;JDK是提供给Java开发人员使用的&#xff0c;其…

[论文阅读]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 在这项工作中&#xff0c;我们介绍了RePlug&#xff08;Retrieve and Plug&#xff09;&#xff0c;这是一个新的检索增强型…

数图信息科技邀您共赴第二十五届中国零售业博览会

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

DeepSeek智能时空数据分析(三):专业级地理数据可视化赏析-《杭州市国土空间总体规划(2021-2035年)》

序言&#xff1a;时空数据分析很有用&#xff0c;但是GIS/时空数据库技术门槛太高 时空数据分析在优化业务运营中至关重要&#xff0c;然而&#xff0c;三大挑战仍制约其发展&#xff1a;技术门槛高&#xff0c;需融合GIS理论、SQL开发与时空数据库等多领域知识&#xff1b;空…

论文导读 - 基于大规模测量与多任务深度学习的电子鼻系统实现目标识别、浓度预测与状态判断

基于大规模测量与多任务深度学习的电子鼻系统实现目标识别、浓度预测与状态判断 原论文地址&#xff1a;https://www.sciencedirect.com/science/article/abs/pii/S0925400521014830 引用此论文&#xff08;GB/T 7714-2015&#xff09;&#xff1a; WANG T, ZHANG H, WU Y, …

Webug3.0通关笔记17 中级进阶(第01-05关)

目录 第一关 出来点东西吧 1.打开靶场 2.源码分析 3.源码修正 4.文件包含漏洞渗透 第二关 提交方式是怎样的啊&#xff1f; 1.打开靶场 2.源码分析 3.渗透实战 &#xff08;1&#xff09;bp改包法 &#xff08;2&#xff09;POST法渗透 第三关 我还是一个注入 1.打开…

React自定义Hook之useMutilpleRef

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