函数栈帧的创建和销毁

目录

引言:

1,函数栈帧的概念

2,函数栈帧的创建与销毁过程

2.1预备知识

2.2main函数栈帧的创建 

2.2.1push  ebp

2.2.2mov    ebp,esp

2.2.3sub    esp,0E4h

2.2.4push     ebx ;push     esi;push     edi

2.2.5lea      edi,[ebp+FFFFFF1Ch]

2.2.6mov   ecx,39h

2.2.7mov         eax,0CCCCCCCCh 

2.2.8rep stos    dword ptr es:[edi]

2.3局部变量的创建

2.3.1mov      dword ptr [ebp-8],0Ah

2.3.2mov      dword ptr [ebp-14h],14h

2.3.3mov      dword ptr [ebp-20h],0

2.4Add函数的调用

2.4.1mov         eax,dword ptr [ebp-14h]

2.4.2push        eax

2.4.3mov         ecx,dword ptr [ebp-8]

2.4.4push        ecx

2.4.5call        00B810E1

2.4.6Add函数栈帧的创建 

2.4.6.1push        ebp

2.4.6.2mov         ebp,esp

2.4.6.3sub         esp,0CCh

2.4.7依次执行push        ebx ;push     esi ;push        edi

2.4.8lea         edi,[ebp+FFFFFF34h]

2.4.9依次执行mov         ecx,33h;mov         eax,0CCCCCCCCh

2.4.10rep stos    dword ptr es:[edi]

2.4.11mov         dword ptr [ebp-8],0

2.4.12mov         eax,dword ptr [ebp+8] 

2.4.13add         eax,dword ptr [ebp+0Ch] 

2.4.14mov         dword ptr [ebp-8],eax 

2.4.15mov         eax,dword ptr [ebp-8]

2.5函数栈帧的销毁

2.5.1依次执行pop         edi;pop         esi;pop         ebx 

2.5.2mov         esp,ebp

2.5.3pop         ebp 

2.5.4ret

2.5.5add         esp,8 esp

2.5.6mov         dword ptr [ebp-20h],eax 

结语:

1.局部变量是怎么创建的?

2.为什么局部变量的值是随机值?

3.函数是怎么传参的?传参的顺序是怎样的?

4.形参和实参是什么关系?

5.函数调用是怎么做的?

6.函数调用结束后是怎么返回的?


引言:

很多伙伴在学习C语言时侯,可能会有很多的困惑?

比如:

  • 局部变量是怎么创建的?
  • 为什么局部变量的值是随机值?
  • 函数是怎么传参的?传参的顺序是怎样的?
  • 形参和实参是什么关系?
  • 函数调用是怎么做的?
  • 函数调用结束后是怎么返回的?

其实我们只要知道了函数栈帧的创建和销毁上述这些问题自然就迎刃而解了,学习这部分知识就相当于在修炼我们的内功,让自己理解知识的能力更上一层楼。

注:在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现(本文基于VS2013编译器调试)。


1,函数栈帧的概念

函数栈帧是指在函数调用时,为该函数分配的一块内存区域,用于存储函数的局部变量,参数和其它相关信息。每个函数调用都会创建一个新的函数栈帧,函数执行完毕后,函数栈帧会被销毁。

2,函数栈帧的创建与销毁过程

🎈演示代码:

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

2.1预备知识

我们知道,每一个函数调用,都要在栈区创建一个空间,假设我们现在调用main函数,就要在栈区内存上面为main函数分配一块空间,我们就称这块空间是为main函数开辟的一块函数栈帧。那这块空间是由谁来维护的呢?它是由两个寄存器来维护的,一个是ebp(栈底指针),一个是esp(栈顶指针)。那我们现在就可以这样理解,正在调用哪个函数,那esp和ebp维护的就是哪个函数的函数栈帧,esp和ebp之间的空间就是为这个函数调用所分配的空间。

接下来,我们执行main函数,鼠标点击工具栏中的调试,窗口,调用堆栈,我们就可以从函数的调用堆栈里边看见main函数被调用了。

到这儿,大家可能会有点困惑,main函数被谁调用了呢?这时候我们让代码继续往下走,当这个代码执行完的时候,我们就可以看见是__tmainCRTStartup() 这个函数内部调用了我们的main函数,而__tmainCRTStartup() 这个函数又被mainCRTStartup()这个函数调用了。由此,我们可以得知,在VS2013中,main函数也是被其他函数调用的。

我们知道,在使用内存的时候,每一次函数调用都要在栈区上分配空间。那我们接着分析这段代码,开始调用main函数后,就为main函数分配了栈帧,调用完后接着往下走,马上调用了Add函数,这时候也要为Add函数分配栈帧,分配完后esp指针和ebp指针就开始维护Add函数的函数栈帧。我们还应该清楚的一点是,在main函数调用之前它的下面也为__tmainCRTStartup()函数和mainCRTStartup()函数分配了空间。

2.2main函数栈帧的创建 

接下来我们研究一下这个函数具体是怎么调用的。

第一步,按F10调试,然后右击鼠标点击转到反汇编,这个时候就可以看到C语言所对应的汇编代码了,因为我们一会儿要看具体的地址,内存的布局,而现在显示的是'a','b','c'这样的符号名,不好观察,所以我们可以把显示符号名这个选项卡去掉,去掉后里边就变成了诸如[ebp - 8]这样的地址,方便我们更好的观察。

 那这些代码又该如何去理解呢?其实很简单,接下来,跟着博主的思路继续往下走。

现在我们调用main函数,前面说过,再调用main函数之前,还调用了一个函数__tmainCRTStartup(),而现在我们已经进到main函数,马上要调用main函数了,那另外一个调用main函数的那个函数的函数栈帧肯定已经创建好了,现在进入main函数,进去后的指令执行步骤如下:

2.2.1push  ebp

push叫做压栈操作,就是把ebp压到__tmainCRTStartup()函数的函数栈帧上边,因为esp总是维护的栈顶,所以,当push完后,esp会指向刚才压进去的ebp。

2.2.2mov    ebp,esp

这步操作是将esp的值赋给ebp,那栈底的ebp此时也会指向刚才压进去的ebp。

2.2.3sub    esp,0E4h

这步的操作是给esp的值减去十六进制的0E4h,esp的值就会变小,这意味着此时的esp不再指向刚才压进去的ebp,而是指向了上边的某一块区域,此时,就会为main函数预开辟一块空间。

2.2.4push     ebx ;push     esi;push     edi

这三条指令会将ebx,esi,edi依次压到栈顶,而push操作每执行一次,esp的值就会发生变化,等这三条指令执行完毕后,esp就会指向栈顶,即edi的位置。

2.2.5lea      edi,[ebp+FFFFFF1Ch]

这条指令的意思是将[ebp+FFFFFF1Ch]加载到edi里边去,相当于给edi里边放了一个地址。

2.2.6mov   ecx,39h

这条指令将39h的值放到ecx这个寄存器里边。

2.2.7mov         eax,0CCCCCCCCh 

这一步是将CCCCCCCC这样的值放到eax里边。

2.2.8rep stos    dword ptr es:[edi]

真正产生效果的是这条指令,意思是要把从刚刚edi这个位置开始向下的39h这么多的空间全部都改成CCCCCCCC这样的内容,也就是将刚刚为main函数预开辟的空间里边的内容全部初始化成CCCCCCCC这样的内容。

当我们执行完以上这些指令以后,为main函数栈帧的开辟就已经准备完了。

2.3局部变量的创建

2.3.1mov      dword ptr [ebp-8],0Ah

意思是把0Ah这个十六进制数字放在[ebp-8]的位置,0A就是十进制的10,ebp-8的位置就是为a变量开辟的一块空间,由此我们也知道了为什么定义变量的时候要给它初始化,因为如果不初始化,它里边放的就是CCCCCCCC这样的随机值。

2.3.2mov      dword ptr [ebp-14h],14h

意思是将十六进制的14h放在ebp-14h的位置上,即为b变量开辟了一块空间,里边放的是十进制的20。

2.3.3mov      dword ptr [ebp-20h],0

意思是将0放在ebp-20h的位置上,即为c变量开辟一块空间,里边放的是十进制的0。

2.4Add函数的调用

我们知道函数调用就要传参,那在这儿它是怎么传参的呢?我们一起来看一看。

2.4.1mov         eax,dword ptr [ebp-14h]

意思是把ebp-14h的值放到eax里边去,而在上边我们分析过[ebp-14h]的位置就是b变量的位置,b变量里边存储的是十进制的20,所以就是把十进制的20放到eax里边去。

2.4.2push        eax

push即压栈,就是把eax压到栈顶上去,而eax里边放的是20,即把20压栈到了栈顶上去。此时esp指向了eax的位置。

2.4.3mov         ecx,dword ptr [ebp-8]

意思是把ebp-8的值放到ecx里边去,而上边我们也分析过ebp-8的位置就是a变量的位置,a变量里边存储的是十进制的10,所以就是把十进制的10放到ecx里边去。

2.4.4push        ecx

意思是把ecx里边的值压栈到栈顶上去,即把10压倒了栈顶,此时esp指向了ecx的位置。

2.4.5call        00B810E1

call指令是调用的意思,此时,我们按F11进入到它的内部,可以看到它把call指令的下一条指令的地址压到了栈顶,为什么要记住这个地址呢,我们可以想象一下,call指令一调用就会马上去调用add函数,而add函数调用完后它要回到call指令的下一条指令继续往下执行,所以add函数回来的时候就会找到这个地址,再从这个地址继续往下执行。

执行到这的时候我们在按F11来到真正的Add函数内部:

2.4.6Add函数栈帧的创建 

可以看到前面的汇编代码和main函数内部的一模一样,这是在为Add函数创建函数栈帧。

2.4.6.1push        ebp

意思是将main函数栈帧底部的ebp压到栈顶,此时esp指向栈顶的ebp。

2.4.6.2mov         ebp,esp

将esp的值赋给ebp,此时,ebp也指向了和esp同样的位置。

2.4.6.3sub         esp,0CCh

给esp减去0CCh,此时,esp就会指向上边的某一块区域,这时,Add函数的函数栈帧也创建好了。

2.4.7依次执行push        ebx ;push     esi ;push        edi

这三条指令会将ebx ,esi ,edi依次压倒栈顶,此时esp指向了栈顶的edi。

2.4.8lea         edi,[ebp+FFFFFF34h]

将ebp+FFFFFF34h这个地址加载到edi里边去。

2.4.9依次执行mov         ecx,33h;mov         eax,0CCCCCCCCh

分别将33h和0CCCCCCCCh 的值放到ecx和eax里边去。

2.4.10rep stos    dword ptr es:[edi]

将从edi这个位置开始向下到ebp之间这所有的空间的内容初始化成CCCCCCCC。

🍂int z = 0;

2.4.11mov         dword ptr [ebp-8],0

把0放到ebp-8的位置上去,即给z开辟一块空间,ebp-8指向的就是z这块空间。

🍂z = x + y;

2.4.12mov         eax,dword ptr [ebp+8] 

把ebp+8的值放到eax寄存器里边去,而ebp+8指向的这块空间刚好是ecx,它里边放着的是10,所以,eax寄存器里边放着的就是10。

2.4.13add         eax,dword ptr [ebp+0Ch] 

给eax里边加上ebp+0Ch的值,而ebp+0Ch指向的是eax,它里边放着的是20,所以,eax现在就变成了30。

2.4.14mov         dword ptr [ebp-8],eax 

把eax的值放在ebp-8的位置上去,ebp-8即z空间,所以,现在z里边放的就是30。

🍂return z;

2.4.15mov         eax,dword ptr [ebp-8]

把ebp-8的值放在eax里边去(eax是寄存器,它不会因为程序退出就销毁里边的值),ebp-8就是z空间,里边放的是30,所以就是把30放在eax寄存器里边去。

2.5函数栈帧的销毁

2.5.1依次执行pop         edi;pop         esi;pop         ebx 

pop是出栈操作,意思是依次将edi,esi,ebx  弹出栈顶。

2.5.2mov         esp,ebp

把ebp赋给esp,此时esp不在指向栈顶,而是指向和ebp同样的位置。

2.5.3pop         ebp 

此时ebp和esp都指向了栈顶,而这步操作就是把ebp弹出,而ebp弹出后esp也不应该指向这儿,而是向下移一个位置,而移动后esp刚好就指向了前面call指令记住的地址。

2.5.4ret

这条指令执行后刚好就会回到call指令的下一条指令的地方,退出Add函数,回到main函数里边。

回到main函数里边后,main函数的函数栈帧又是由esp和ebp来开始维护。

2.5.5add         esp,8 esp

这是esp指向栈顶,加上8就会向下移动到指向edi的位置,相当于把形参x,y还给操作系统。

2.5.6mov         dword ptr [ebp-20h],eax 

把eax的值放到ebp-20h的位置上,eax寄存器里边放的是30,ebp-20h的位置即c变量的位置,也就是将30赋给c变量。

结语:

现在我们就可以解答文章开头的疑惑了:

1.局部变量是怎么创建的?

局部变量的创建是首先为这个函数分配好栈帧空间,栈帧空间里边初始化好一部分空间之后给局部变量在栈帧里边分配一点空间,这就是局部变量的创建。

2.为什么局部变量的值是随机值?

因为如果不初始化的话,里边的值是我们放进去的,所以就是随机值,如果给局部变量初始化的话,就会把这个随机值给覆盖了。

3.函数是怎么传参的?传参的顺序是怎样的?

当我们要去调用函数的时候,还没有调用的时候就已经用push指令把这两个参数从右向左开始压栈进去了,当真正进入形参函数的时候,在Add函数栈帧里边通过指针的偏移量找回来找到了我们的形参。

4.形参和实参是什么关系?

形参是在压栈的时候开辟的空间,它和实参只是值是相同的,但空间是独立的,所以形参是实参的一份零时拷贝,改变形参不会影响实参。

5.函数调用是怎么做的?

见上文

6.函数调用结束后是怎么返回的?

函数在调用之前就把call指令的下一条指令的地址存进去了(即把ebp调用这个函数的上一个函数的栈帧的ebp存进去),当函数调用完要返回的时候,弹出ebp就能够找到上一个函数调用的ebp,然后指针往下走的时候就能够找到esp的地址,回到我们的栈帧空间,返回值是通过寄存器的方式带回来的。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++STL的迭代器(iterator)

一、定义 迭代器是一种检查容器内元素并且遍历容器内元素的数据类型。 【引用自&#xff1a;C迭代器&#xff08;iterator&#xff09;_c iterator_NiUoW的博客-CSDN博客】迭代器是一个变量&#xff0c;相当于容器和操纵容器的算法之间的中介。C更趋向于使用迭代器而不是数组下…

day01_matplotlib_demo

文章目录 折线图plot多个绘图区绘制数学函数图像散点图scatter柱状图bar直方图histogram饼图pie总结 折线图plot import matplotlib.pyplot as pltplt.figure(figsize(15, 6), dpi80) plt.plot([1, 0, 9], [4, 5, 6]) plt.show()### 展现一周天气温度情况 # 创建画布 plt.figu…

【C++】引用’‘的深入解析

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

C语言学习系列->动态内存管理

文章目录 前言概述&#x1f6a9;malloc and free&#x1f51c;malloc&#x1f51c;free &#x1f6a9;calloc and realloc&#x1f51c;calloc&#x1f51c;realloc 前言 要想学好数据结构&#xff0c;在C语言学习过程中就需要把指针、结构体和动态内存管理学好。在前面的文章&…

makesense在线yolov5标注

文章目录 一、创建图片文件夹和label.txt二、在线标注数据 参考文章博主&#xff1a;风吹落叶花飘荡 一、创建图片文件夹和label.txt 创建一个放置图片的文件夹images&#xff0c;存放需要标注的图片&#xff08;图片最好重命名为1,2,3…避免后面混淆&#xff09; 创建label.t…

C++标准模板(STL)- 类型支持 (运行时类型识别,type_info )

运行时类型识别 定义于头文件 <typeinfo> 含有某个类型的信息&#xff0c;由实现生成。​​这是 typeid 运算符所返回的类。 std::type_info 定义于头文件 <typeinfo> class type_info; 类 type_info 保有一个类型的实现指定信息&#xff0c;包括类型的名称和…

我单方面宣布:腾讯云服务器优惠价格(双11特价)

2023腾讯云双十一服务器优惠价格表多少钱一年&#xff1f;轻量服务器2核2G3M、2核2G4M、2核4G5M、4核8G12M、8核16G18M、16核32G28M和云服务器CVM标准型S5实例优惠价格&#xff0c;腾讯云百科今年双11服务器价格会在当前的价格基础上享受个9折优惠&#xff0c;可领券 https://c…

正则表达式之学习笔记

正则表达式学习笔记 一、概念二、正则表达式组成三、常见的正则表达式3.1 .匹配任意字符3.2 * 匹配前一个字符的0个或多个实例3.3 ^ 匹配输入字符串的开头3.4 $ 匹配行尾3.5 [] 匹配字符集合\<\> 精确匹配符号 一、概念 正则表达式是由一系列特殊字符组成的字符串&#…

Linux---(三)基本指令大全

前提引入&#xff1a;历史上先出现的键盘还是鼠标&#xff1f; 答案&#xff1a;键盘 ✨所以刚开始的时候绝对没有图形化界面&#xff0c;因此操作系统刚开始兴起的时候绝对没有图形化界面&#xff0c;因为当时没有鼠标。 ✨因为没有图形化界面&#xff0c;只有键盘&#xff0c…

vue项目编译、打包、部署服务器运行

在vue项目执行npm run build,生成dis目录 打包dis上传 安装npm install -g http-server或者apt install node-http-server 运行http-server

postman自动化运行接口测试用例

做过接口测试的人&#xff0c;应该都知道postman &#xff0c;我们在日常的时候都可以利用postman做接口测试&#xff0c;我们可以把接口的case保存下来在collection里面&#xff0c;那么可能会有这样的需求&#xff0c;我们怎么把collection的用例放到jenkins中定时执行呢&…

预测宝可梦武力值、分类宝可梦

regression case 股票预测 无人车看到的各种sensor 影像镜头看到马路上的东西作为输入&#xff0c;输出就是方向盘角度等等的操纵策略 scalar 标量 这个是热力图&#xff0c;相当于你的XYZ但是Z用颜色表示了 closed-form solution 闭合解 learning rate事先定好的数值 在lin…

推荐一款简单好用的Bug管理软件

软件开发的速度和质量是企业成功的关键因素&#xff0c;然而随着软件项目的复杂性增加&#xff0c;Bug的管理变得越来越困难。为了解决这个问题&#xff0c;Zoho公司推出了一款强大的Bug管理软件&#xff0c;帮助企业在一个地方记录和跟踪Bug&#xff0c;提高修复效率。 这款名…

10.18~10.22数电第二次实验

频分复用 同一个时间共用一个频道&#xff0c;只不过频率不同&#xff0c;所以互不影响 时分复用 不同时间公用一个频道&#xff0c;轮流使用 时分复用&#xff08;TDM&#xff0c;Time-division multiplexing&#xff09;就是将提供给整个信道传输信息的时间划分成若干时间…

SQL基础语法总结(查询)

学习网站&#xff1a;https://www.w3schools.com/sql/&#xff0c;提供在线编程 以下内容仅SQL常见语法总结 数据 Customers表 Products表 OrderDetails表 Orders表 Shippers表 Employees表 选择查询 SELECT select语句用来从头数据库中选择数据 SELECT column1, column2,…

交换机/防火墙-基础配置-23.10.11

1.MAC地址 交换机在给主机之间传递信息包时&#xff0c;通过MAC地址来标识每台主机 主机间发生信息包交换时&#xff0c;交换机就会将通信过的主机的mac地址存下 dis mac-address 交换机转发的数据包中&#xff0c;会包含一个目标MAC&#xff0c;交换机识别数据包中的目标MA…

可视化数学分析软件 MATLAB R2021b mac中文版软件介绍

MATLAB R2021b mac作为数学类科技应用软件中首屈一指的商业数学软件&#xff0c;可以帮助您进行矩阵运算、绘制函数和数据、实现算法、创建用户界面、连接其他编程语言的程序等,主要应用于工程计算、控制设计、信号处理与通讯、图像处理、信号检测、金融建模设计与分析等领域。…

自己动手写编译器:c 语言模板中的输入模块设计

使用过“框架”的同学都能感受到“框架”带来的方便。所谓”框架“本质上就是一系列代码安排帮助我们完成脏活累活&#xff0c;或者复杂的工作流程后&#xff0c;把处理结果交给我们提供的代码。本节我们要完成的 c 语言模板也是一个框架&#xff0c;它也需要做一系列脏活累活&…

vue重修之路由【上】

文章目录 单页应用程序: SPA - Single Page Application路由简介Vue Reouter简介VueRouter的使用&#xff08;52&#xff09;组件的存放目录问题组件分类存放目录 路由的封装抽离 单页应用程序: SPA - Single Page Application 单页面应用(SPA): 所有功能在 一个html页面 上 单…

【Java 进阶篇】深入了解 Bootstrap 全局 CSS 样式

Bootstrap 是一个流行的前端框架&#xff0c;以其强大的全局 CSS 样式而闻名。这些样式能够帮助开发者快速创建漂亮的、响应式的网页&#xff0c;而无需从头编写复杂的 CSS。在本文中&#xff0c;我们将深入探讨 Bootstrap 的全局 CSS 样式&#xff0c;适合初学者&#xff0c;帮…