【C语言】函数栈帧的创建与销毁

【C语言】函数栈帧的创建和销毁 函数栈帧的创建和销毁 阅读详情

大家在学C的时候是否有过这些疑问?

1.局部变量是如何创建的?

2.为什么局部变量不初始化内容是随机的?

3.函数调用时参数时如何传递的?

4.传参的顺序是怎样的?

5.函数的形参和实参分别是怎样实例化的?

6.函数的返回值是如何带会的?

想要了解函数栈帧,就要先了解这些问题,想要了解这些问题就要了解寄存器...

是不是感觉一环套一环啊?

直接上干货~

先要了解这两个寄存器

1.ebp 2.esp

这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧,(注意:不同版本的编译器,之间略有差异)

他是怎么 来维护的呢?

每一个函数调用,都要在栈区上开辟空间

我们来看代码,分析其过程

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

一下就是main函数在栈区上开辟的空间、

这块空间就被称为栈帧

那么这块空间是怎么被维护的呢?

 就是由ebp和esp维护起来的

什么意思呢?

就是说在我当前去调用这个main函数的时候,这时候就会为main函数分配空间,而这块空间就由这个esp和ebp来维护,正在调用哪个函数,哪个函数就由esp和sbp来维护

如上代码

如果调用Add函数,ebp和esp就会去维护他的栈帧

esp叫栈顶指针(存放被维护空间的栈帧顶部地址)

ebp是栈底指针

栈区是由高地址向低地址处使用空间

这是要明白的基本前提~

如果像在编译器上看到现象的话,可以去打开调用堆栈

(这里作者用的VS2022)

 这里其实可以看到main函数被调用了,可困惑的是,他被谁调用了呢?

 继续往下走可以看到是mainCRTStartup等函数逐个调用

在主函数里的return 0可以看看出,他的返回值交给了__scrt_common_main——seh这个函数(转到反汇编里依然可以看到)

也就是说,其实main也是被其他函数调用的,并且将返回值交给调用main函数的函数

那么在main函数被调用之前,也会为调用main函数的函数在栈区上开辟一块空间

如下图红色框框:

如果在进入Add函数的时候,栈上也会开辟一块空间给他,这就是Add函数的栈帧

如下图蓝色框:

 这就是大致逻辑,具体是什么样呢?

咱们深入剖析(这里由于新版本编译器 如VS2022 不能更好的反应其内部情况,这里用VS2010)

这回进入汇编代码,来一探究竟

为了更好的观察内存布局(地址),可以去掉显示符号名

刚刚我们看到在进入main函数之前也会调用到其他函数,那么此时,esp和edp维护的空间就是他

 这副图的前提是,下面是高地址,上面是低地址,因为栈区内存的使用原则是高地址向低地址,所以一会向上使用空间

接下来看会汇编代码

这里有个 push  ebp ,push的意思就是压栈,也就是说,把ebp的地址压入到栈区中

如下图蓝色框

 

 因为esp是维护栈顶的所以此时esp的位置也会发生变化

上面是低地址,所以此时esp的地址会变小

下来看mov

 这条指令的意思是把esp的值赋给ebp

此时,ebp将不再指向原来的位置,而是和esp指向同一位置,如下图

 

下来看sub

 

 sub是减的意思,就是给esp减上一个0E4h的值,这个值是一个8进制数字

那么,esp的地址将会减少,也就是说,他会指向上方和原来位置相差0E4h距离的位置

其实这个时候,esp,ebp已经不再维护原来调用main函数的函数的空间,他有了新的维护空间,这块空间实际上就是main函数的栈帧

 

 接下来又遇到了3个push 

也就是压栈压了三个值ebx,esi,edi,同时,esp作为栈顶维护空间,也会变化,移向栈顶

继续往下分析

 这里有个lea,实际上他是这几个单词的缩写load effecitive address(加载有效地址)

显示符号名可以看到

 这里ebp - 0E4h,相当于给ebp减去了0E4h这么多的地址,就和之前的sub一样

然后再将地址赋给edi

此时,ebp 就指向了紫色箭头的位置

接下里看这三步

 两个mov ,第一个是把39h这个值赋给ecx,第二个是把0CCCCCCCCh这个值赋给eax

但其实真正产生效果的是下面这个地方

 他是干甚的呢?

 他其实是把从edi开始向下39h(ecx)的空间全部赋值成eax,也就是赋值成0CCCCCCCCh这个值

word表示2个字节,dword表示双字节,也就是四个字节,每次向下将修改4个字节的内容

向下执行可以看见数据已被修改

也就是这个样子

 这时,其实才完成了main函数栈帧的开辟

接下来才是真正实现有效代码

 int a = 10;转化成汇编代码就是他下面这段代码

 意思就是将0Ah这个值放到ebp-8的这个位置的地址处去

 以上就是a 的实际位置

 不知道大家以前有没有用printf这个函数打印出“ 烫烫烫 ”实际上就是打印的cccccccc也就是随机值。

小端这里显示放入a变量如下图

接下来就是把b放入内存中了

 这里就是把14h这个值放入到ebp - 14h这个地址处

至于他为什么存放在这个位置,这都取决于编译器

接着存放c变量 

 

就是把0这个值放在ebp - 20h这个地址处

这时候abc这三个变量是如何创建想必心里因该有谱了 

所以局部变量是如何创建的呢?

就是在栈上为函数开辟一块空间,也就是这个函数的栈帧,然后继续在这个函数的栈帧内部找空间开辟,来存放局部变量

当main函数创建好了以后,就因该调用Add函数了

 第一段代码,mov作用是把ebp-14h地址(这里的[   ]表示地址)指向的值放到eax里面去,还记得eax是什么吗?

eax实际上就是b啊,显然这里在实现传参

 然后push,这里就是压栈,将eax压入栈顶

 同时这里esp也会发生变化

下一步mov是把ebp-8这个位置处的地址指向的内容放到ecx里面去 ,ebp-8实际上指向的就是a,

那么现在ecx里面的值就是10

下一步

 继续push,把ecx压入栈顶

这些个过程实际上就是函数传参的过程

下一步call指令实际上就是在调用

 看一下红色框框起来的call指令的地址

并且可以看到a值地址的上方

值变成了00C21450,实际上就是call指令的下一条指令的地址,将他压栈

 

 为什么要这么做呢?

其实走到call指令的时候,程序的执行就已经进入到了Add函数里面,可Add函数执行完了因该怎么回到main函数继续执行呢,这里的00c21450实际上就记住了Add执行完下一步的地址,帮助找回main函数下一步执行的程序

此时,来到Add函数的内部 

有没有发现,int z = 0;以上的内容和main函数初始化的内容是一样的,实际上这里就是为Add函数创建栈帧

 

接下来创建z

 

 将0这个值放到ebp-8指向的那块空间

 下来执行计算:

 这里有人可能就疑惑了,这x,y在哪创建的啊,这怎么执行计算啊?

别急,继续往下看

mov,将ebp+8指向的值放到eax里面去

ebp+8实际上就是指向a 的值:10 

add,给eax处的值加上ebp+0Ch指向的值,ebp+0Ch指向的就是b的值:20

mov,将eax的值放入到ebp-8指向的空间,ebp-8就是z啊

仔细回想一下刚刚的过程,我们完成这一系列的过程,在进入Add函数以后,真的有去创建x ,y吗?

并没有,再执行z = x + y;这条指令的时候,是怎么找到x,y的值的?就是通过ebp+上一个值,找到在还没有调用Add函数之前就已经压栈压进去的ecx和eax,并且不难发现Add(a,b)传参的时候是从右向左依此压栈,先传b,再传a,归根结底这里就是对原来main函数里面的实参a,b进行的一份临时拷贝并压入栈中

 想象一下,这里你改变形参会影响实参a,b吗?并不会,因为这时形参已经有了自己独立的空间,寄存器ecx和寄存器eax

#形参是实参的一份临时拷贝

目前只是调用并计算了这些值,还没有返回,怎么返回呢?而且出了Add函数这些值不就销毁了吗?

我们看下一步

 return z; 意思就是把ebp-8 (刚刚计算得到的z的值) 指向的值放到寄存器eax里面去,为什么要放在eax里面去呢,因为一会出了函数z就销毁了,而存进eax里就不会,为什么呢?这里可是寄存器,并不会因为程序退出而销毁

下一步

return z;完了以后出现了三个pop ,pop的意思是 弹出(出栈),也就是说,这里将edi,esi,ebx的值全部弹出栈栈区,此时esp也就会发生变化

接下来Add函数已经完成了它的使命,那么这块空间就会被回收,怎么回收呢,看下一条指令

 这里mov,就是把ebp赋给esp,那么esp就不会指向原来的空间,而是和ebp指向同一空间,

下一步

 

 pop这里将ebp弹出空间,这里的ebp存的是main函数的地址

什么意思呢,也就是之前的存的ebp就被释放掉了,同时这个指向ebp的指针也会从这个main函数的地址,回到原来他指向的位置

 此时就顺顺利利的回到了原来main函数的栈帧

 下一条指令ret

 意思就是返回,那么程序的下部该返回到哪里去呢?

大家是否还记得

main函数的栈顶上出了有刚刚的main函数的ebp被弹出,下一条就是之前call指令 ,而这条指令完成的任务就是将call指令的下一条指令的地址存起来,怎么回去呢,此时这里正好存放这这条地址,而ret就是要返回到call指令所指向的下一条指令的地址处去

现在因该就可以明白了当时为什么要存这条地址,不仅要走出去,还要回得来

这就是函数栈帧创建与销毁的一个严谨的过程,

找回到原来的地址,esp也将指向下一个位置

 此时ecx和eax的值还有用吗?看下一条汇编指令

这里将esp的地址加8,esp将向下挪动8个字节,那么此时ecx,eax也将还给操作系统

 现在形参怎么销毁,什么时候销毁,想必心里也因该清楚了

下一条指令

 mov将eax的值放在了ebp-20h指向的空间位置处,c变量不就是在ebp-20h地址处吗,

所以此时,Add函数处理的值也就回到了c变量处

////

 以上就是Add函数的创建和销毁的全过程,那么想必因该也能明白了main函数的创建与销毁过程.

开头的问题应该也就清楚了.

码字不易~

【C语言】程序员筑基功法——《函数栈帧的创建与销毁》 汇编角度带你走进函数栈帧 阅读详情

相关推荐

AGV舵轮选型实战:从核心参数到精准计算的完整指南

本文提供了一份详尽的AGV舵轮选型实战指南,强调从经验决策转向数据驱动。文章系统讲解了如何将工况需求转化为核心参数,并通过牵引力、扭矩、功率等关键计算步骤,指导工程师完成精准选型,避免因选型不当导致的运行故障与效率低下。

醉三国 495

一篇文章让你搞懂C语言函数栈帧的创建与销毁过程

*eax “累加器” 它是很多加法乘法指令的缺省寄存器。ebx "基地址"寄存器, 在内存寻址时存放基地址。ecx 计数器,是重复(REP)前缀指令和LOOP指令的内定计数器。edx 总是被用来放整数除法产生的余数。esi 源索引寄存器edi目标索引寄存器ebp (栈底指针)“基址指针”,存放的是地址,用来维护函数栈帧。

优快云官方认证C/C++研发领域优质创作者 优快云官方2023年成都赛道Top3 第16届蓝桥杯C/C++软件赛四川赛区二等奖 带你玩转C/C++ 数据结构与算法 Linux系统编程/Linux网络编程 MySQL数据库 软件测试开发 1968

手把手教你用Cadence Virtuoso仿真一个5GHz CMOS分频器:从原理图到后仿,避开那些教科书上没写的坑

本文详细介绍了在Cadence Virtuoso环境中设计5GHz CMOS分频器的完整流程,从原理图设计到后仿真优化,特别关注实际工程中容易被忽视的关键问题。内容涵盖分频器核心指标解析、锁存器拓扑选择、接口设计匹配以及后仿真优化策略,为射频集成电路设计提供实用避坑指南。

weixin_26934905的博客 262

函数栈帧的创建与销毁

我们学习语法学习编程逻辑都是基于封装好的知识上来进行学习,知其然而不知其所以然,如果想要更好的掌握理解所学知识,我们对知识应该有一个更深层次理解,了解过基本语法的伙伴应该都有过下面这些疑问,这些问题怎么去理解,这里就需要我们去了解函数栈帧的创建与销毁; 1.局部变量是怎么创建的? 2.为什么局部变量不初始化的值是随机值? 3.函数是怎么传参的?传参的顺序是怎么样的? 4.形参和实参是什么关系? 5.函数调用是怎么做的? 6.函数调用结束后怎么返回的? 如果上面问题你都能了解,在此让我叫

weixin_59406869的博客 2278

用STM32F103单片机控制电调制动无刷电机

用电调控制无刷电机制作遥控飞艇 蓝牙控制 调控电调 无刷电机 用电调控制无刷电机制作遥控飞艇 代码区 mian.c #include "delay.h" #include "sys.h" #include "usart.h" #include "usart3.h" #include "timer.h" #include "led.h" #include "key.h" int main(v...

子非鱼 的博客 2万+

J-LINK下载程序出现No Cortex-M Device found in JTAG chain…问题,最全7种解决方法

J-Link下载程序出现No Corect 等的问题的7种方法 后来编辑JLinkSettings.ini文件,找到"Device"项,将其更改Device="Cortex-M0+"

cjmsea的博客 5611

函数栈帧的创建和销毁(详解)

函数栈帧的创建和销毁(详解)

越努力越幸运 1196

RTX4090上跑CenterNet训练

参考实现 url=https://github.com/xingyizhou/CenterNet.git commit_id=5b1a490a52da57d3580e80b8bb4bbead9ef2af96

遗失的神庙 1621

汇编_esp,edp寄存器

内存中的堆:用户操作带来的主动开辟,由用户操作清空 内存中的栈:用户操作中带动的函数执行带来的操作系统自行开辟,函数运行完自动清空 某进程的堆栈存在于为它开辟好的内存空间中,堆由低往高长,栈由高往低长 esp 一直指向当前栈顶 edp 储存某时刻的esp 开始时的 push edp是压入上一层栈的 edp内容 到栈底地址 ; 最后的 pop edp 是 弹出栈底地址的数据到 edp中; ret x:是弹出返回地址,并让ESP 上移 x (参数占位); ...

redglare的博客 1749

【backtrader保姆级教学】投资组合回测

method1:探索投资组合的最有方案,使用蒙特卡洛模拟Markowitz模型。# 提取最小波动组合对应的权重, 并转换成Numpy数组。# 绘制收益-标准差的散点图,并用颜色描绘夏普比率。# 在收益-风险散点图中突出风险最小的点。:param any_day: 任意日期。# 将该点坐标显示在图中并保留四位小数。password = “替换密码”# 找到标准差最小数据的索引值。user = “替换账号”# 计算GMV投资组合收益。# 计算每项资产的夏普比率。# 设置无风险回报率为0。

weixin_38132951的博客 2314

Android16 rk3576添加触摸点数上限到40个点

本文介绍了在Android 16系统上实现40点触摸支持的修改方案。相比Android 15,Android 16对API添加有更严格的要求,需要在MotionEvent.java中添加@FlaggedApi批注。主要修改包括:1)在input_framework.aconfig中定义flag;2)在MotionEvent.java中添加40个AXIS_GENERIC轴常量并添加批注;3)将32位指针ID处理改为64位适配;4)更新相关API文档。修改涉及多个核心文件,包括PointerController

tzj1058756809的博客 381

【C语言】函数——栈帧的创建和销毁

✨作者:@平凡的人1 ✨专栏:《C语言从0到1》 ✨一句话:凡是过往,皆为序章 ✨说明: 过去无可挽回, 未来可以改变 目录 前言😄 什么是栈🔑 什么是函数的栈帧🔑 认识相关寄存器和汇编指令🔑 寄存器🔥 相关的汇编指令:🔥 函数的调用堆栈🔑 函数栈帧的创建🔑 分析栈帧的创建:💧 为什么会出现“烫烫烫”:💧 分析main函数中的核心代码:💧 分析Add函数的传参💧 函数调用过程💧 函数栈帧的销毁下🔑 结语✍ 前言😄 好的,各位,我们前面就已经学.

weixin_60478154的博客 3206

知识图谱:综述

第一种理解:知识图谱本质上是语义网络。本体论是语义网络中最重要的一个要素。第二种理解:知识图谱也叫做多关系图,由多种类型的节点和多种类型的边组成。课程以第二种理解为主。

u013250861的博客 478

C语言:底层剖析——函数栈帧的创建和销毁

C语言的使用是面向过程的,面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。所以C语言的程序都是以函数作为基本单位的,如果能够深入理解函数,无疑对于c语言会有更深刻地理解,修炼自己的内功,那么函数是如何调用的?函数返回值是如何返回的?函数的形参是如何传递的…………等等的问题,其实都和函数栈帧有关系!

weixin_51142926的博客 7673

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

7. C语言之反汇编查看函数栈帧的创建与销毁

配置环境反汇编查看函数栈帧的创建与销毁

分享C/C++/Linux/网络/数据库/数据结构 2504

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

【C语言】——函数栈帧的创建与销毁

函数栈帧的创建与销毁,逐语句分析

dab112的博客 1369

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

C语言:函数栈帧的创建和销毁

本文简要介绍了函数的栈帧的创建与销毁,旨在帮助c语言学习阶段的朋友们理解一些之前不太清楚的问题(如传参的本质等)。由于面向新人,加上博主实力平平,一些深度的、细节上的内容不会分享(如汇编指令mov、sub等),如有错误,感谢指正

Yolo_TvT的博客 1021

C语言函数栈帧的创建与销毁(32)

应该是最后一篇了,这一篇相当重要!但是也是最难的一篇,涉及底层正文开始!我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。那函数是如何调用的?函数的返回值又是如何传回的?函数参数是如何传递的?这些问题都和函数栈帧有关系~函数参数和函数返回值临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)保存上下文信息(包括在函数调用前后需要保持不变的寄存器)其实我在Cpp篇内说了返回值具有临时常性,也就是这个原因。

tan180°的博客 2925

【C语言】函数栈帧的创建与销毁(VS2022)

【C语言】函数栈帧的创建与销毁(VS2022)

XJ2536877903的博客 1041

【C语言】-函数栈帧的创建和销毁(详细版)

函数栈帧的创建和销毁和常见汇编指令以及常见寄存器使用

m0_63979882的博客 1565

C语言深入剖析——函数栈帧的创建与销毁

深入理解C语言中函数栈帧的创建与销毁对于掌握程序的执行流程至关重要。函数栈帧存储了函数的参数、局部变量和返回地址等关键信息,每次函数调用时创建,执行完毕后销毁。本篇博客旨在揭开栈帧管理的神秘面纱,通过深入浅出的方式讲解其在程序运行中的角色和影响,帮助读者建立对C语言更深层次的理解,为解决复杂编程问题奠定基础。

wxk2227814847的博客 1288

c语言(函数栈帧的创建和销毁)

函数栈帧的创建和销毁

小马同学的博客 2297

【C语言深入】逐汇编详解函数栈帧的创建和销毁过程

在正式开始之前,必须先要对我们将要用到的一些汇编指令和寄存器做一些了解。不需要深入理解,只需要了解一下它们的作用和含义即可。相关汇编指令数据转移指令,开辟空间并将数据写入空间数据入栈。同时esp栈顶寄存器也要发生改变数据弹出至指定位置,同时esp栈顶寄存器也要发生改变减法命令加法命令函数调用,1.压入返回地址2.转入目标函数通过修改eip,转入目标函数,进行调用恢复返回地址,压入eip,类似pop eip命令相关寄存器通用寄存器,保留临时数据。常用于返回值ebx;通用寄存器,保留临时数据栈底寄存器。

kk702392702的博客 1282
上一篇: 【C语言】如何实现三子棋和五子棋?
下一篇: 【C语言】智能化的扫雷(如何标记,智能展开)
陈亦康
陈亦康 优质创作者: Java技术领域 优质创作者: Java技术领域
博客等级 码龄5年 6万+粉丝 388原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈亦康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值