鹏哥C语言复习——函数栈帧的创建和销毁

目录

演示用代码:

提示:后文讲解时后缀为h的指的是16进制表示

疑惑1:自定义函数、库函数都是在main函数内部调用,那么是什么调用了main函数呢?

疑惑2:如何观察ebp、esp等寄存器的运行?

疑惑3:ebp、esp等究竟是如何运作的?

疑惑4:push操作到底指的是什么意思?

疑惑5:数据是如何存入内存的?

疑惑6:从main函数到main函数所调用的函数,究竟发生了什么?

疑惑7:被调用函数内部发生了什么?

疑惑8:在被调用函数内部,是如何进行运算的?

疑惑9:函数return以后,会发生什么?


演示用代码:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int Add(int a, int b)
{int z = 0;z = a + b;return z;
}int main()
{int a = 10;int b = 20;int c = 0;c = Add(a, b);printf("%d\n", c);return 0;
}

提示:后文讲解时后缀为h的指的是16进制表示

前期学习时,常有一下疑惑:

  • 局部变量如何创建?
  • 为啥局部变量的不完全初始化值是个随机值?
  • 函数如何传参?
  • 传参顺序如何?
  • 函数调用在计算机底底层是如何做到的?
  • 函数调用结束后如何返回的?

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

要理解函数栈帧,就必须要理解 esp、ebp这两个寄存器。这两个寄存器中存放的是地址,这两个地址是用来维护函数栈帧的。

每一个函数调用,都要在栈区创建一块空间。

3f0eaca702334286ba6b9c619b3bf740.png

在栈区的高地址、低地址之间会为main函数开辟一块空间,这块空间就称为main函数的函数栈帧。esp、ebp会随着函数的调用,维护不同的函数栈帧。如果调用到了其他自定义函数(假设存在一个Add函数),那么就从维护main函数变为了维护Add函数。

65bd4c4107944da2804ade9fd948ace0.png

esp通常叫做栈顶指针,ebp通常称为栈底指针。栈区的使用习惯是先使用高地址,再使用低地址;如果再使用空间,是从esp往上使用空间,因此被称之为栈顶指针。

疑惑1:自定义函数、库函数都是在main函数内部调用,那么是什么调用了main函数呢?

在vs2013中,是其他函数调用了main函数。

a1f1ff4653594ddab596655e3850e923.png

main函数是由 __tmainCRTStartup 调用,__tmainCRTStartup 是由 mainCRTStartup 调用的。(即如下图所示)

8efb5673482d44ff9c1ce42829753b66.png

因此,内存的栈区中应该先是在高地址处为  __tmainCRTStartup 和 mainCRTStartup 开辟空间;然后再调用到main函数,为其在较低地址处开辟空间;最后在main函数中调用其他函数,在低地址处为他们开辟空间。

疑惑2:如何观察ebp、esp等寄存器的运行?

05ec1daa6a354ea8b033f9cb91458870.png

在vs2013的代码编写处,鼠标右键弹出功能框,点击转到反汇编那一项,在反汇编窗口查看。

7d3dedf1b2a545c9a64cb8ed5abe017e.png

2bafc87f7d174e50b6c8d9cf7cc43f73.png

关闭反汇编窗口中的显示符号名功能,从本来的 a、b、c 变为和ebp相关的地址。(ebp - 8 还是个地址,详细内容请关注指针板块)

疑惑3:ebp、esp等究竟是如何运作的?

ef626551d2c449ddba6b1609949a7bf3.png

当我们使用了__tmainCRTStartup 函数以后,内存布局如上图所示;进入main函数以后,先是进行了ebp的push操作(压栈操作),通过push操作,会在已经被使用的的内存空间上再开辟一块空间,用来存放ebp寄存器,esp也会随之上移,具体情况如下图示。

3b5c1207a6b249edb04e777ed6fc7698.png

具体的esp的地址变化,也可以通过调试页面的监视、内存窗口进行查看,最后会发现在经过push以后,esp的地址下降了4位(从高位到低位的变化)

984c89c1db4d464f9440a23a4066e90a.png

mov就是把esp的值给到ebp,最后esp、ebp指向了同一个内存地址;sub行意为esp减去0E4h,esp指向了新的内存地址,然后esp与ebp之间的内存空间就是main函数的栈帧;而最后,开辟的main函数栈帧大小即为0E4h的大小。

6b4798902a7e498596a8fc90bbbcc7a3.png

在此之后,又push了ebx、esi和edi三个寄存器,esp也继续往低地址处移动。

91995157cc6e4d98aca085f903d8c044.png

接下来,就进行了lea操作。lea全称为 load effective address ,即加载有效地址。具体就是把后面的[ebp+FFFFFF1Ch](重新打开显示符号名功能,会发现+FFFFFF1Ch就是-0E4h)这个地址,给到edi;也就是把ebx底部、main函数栈帧顶部的那一个地址给到edi。

随后,通过 mov 操作和 rep stos 操作的搭配使用,进行了内存存放数据的修改。具体就是从edi所指位置开始,从低地址往高地址把 39h 个 dword(双字,即4个字节)全部改成 0CCCCCCCCh 这一内容。具体效果如下图所示。

74212ff3d4434d72abfce98857ddad6f.png

30a7eaf41c0a46f4b5635b91c4d333f9.png  

疑惑4:push操作到底指的是什么意思?

push:压栈操作,往栈顶(栈区的顶部,即栈区当前的最低地址处)存放数据。

pop:出栈操作,从栈顶拿出数据。

push和pop 与 数据结构中的栈实现是一样的,可以协同理解。

疑惑5:数据是如何存入内存的?

cc8ea38bb362427891a3a34289b51212.png

int a = 10下面的这条语句是int a = 10 的汇编语句,即是把 0A(16进制表示的10)存入到 ebp所在位置 -8 的位置。具体效果如下图所示。(可以通过内存窗口,自行查看操作效果)

f6c4e8d445b34f2eab8b469a4cb79516.png

这一步操作也解释了,为什么当变量创建后,如果没有赋初值那么打印结果就是一串随机值;就是因为内存中存放的还是CCCCCCC这样的数据,打印的内容也就是CCCCCCCC。而后续的 int b = 20、int c = 0 所完成的汇编操作都是类似的,只是存放的数据不同而已。

疑惑6:从main函数到main函数所调用的函数,究竟发生了什么?

相信聪明的读者已经发现了,在int a = 10那条语句之前的所有汇编语句,好像都还没有什么作用。(用了,但又没有用doge)

f8de04018da74856b651eefb1549522d.png

通过汇编语言,我们不难发现:先是把ebp-14h地址的内容(即b=20)给到了eax,然后将eax寄存器压栈;又把ebp-8地址的内容(即a=10)给到了ecx,然后对ecx寄存器压栈。随着压栈操作,esp也往低地址移动了8个字节(2个寄存器)。

call为调用语句,是把call指令下一条指令的地址信息压栈。(esp继续随之移动)

1a4631352086461cbf5da7c145bf2e18.png

随后再推进一步,就进入了Add函数。(即会发生从下图1到下图2的区别)

0730be46a71b4dd195874da973805d2b.png

图1

4ff63f2257e94172a24d1b8612ca4847.png

图2

疑惑7:被调用函数内部发生了什么?

b8ec89817e834fd2b40767f20d966de3.png

经过了上述一系列操作以后,终于结束了main函数的栈区内涵讲解;换言之,目前为止的所有内存都是main函数的栈帧(除CRTStart…那个函数以外)

fb246042ad9540c6a9b7443c10059364.png

进入add函数以后,先在栈顶push了ebp(此处push的是维护main函数的ebp寄存器,该寄存器指向了main函数下面的ebp寄存器);然后通过mov,让esp、ebp指向同一块内存空间(即存放维护main函数的ebp寄存器);然后esp又往低地址处移动了0CCh个地址,此时esp、ebp两指针间的区域就是Add函数的函数栈帧。最后,通过和main函数相同的办法,让Add函数的函数栈帧存放的是0CCCCCCCCh,并把 int z=0存入Add函数的函数栈帧。

疑惑8:在被调用函数内部,是如何进行运算的?

在上文中的图内我们能看到,z = x + y 语句应该先是把ebp+8 的数据10存入到eax寄存器中;然后再把 ebp+0Ch 的数据20通过控制器和加法器(计组的知识)加到eax当中。最后再把算出来的结果30放入到ebp-8位置的寄存器中。不难发现,实际上函数的参数并未在函数中创建,而是通过压栈操作放到Add函数栈帧的栈底的下面位置,具体如下图所示。这也就是说形参是实参的一份临时拷贝。

e5c01b64dd04408a8baca9edba13b7ed.png

为防止z出了函数被销毁,因此还是得把ebp-8所在位置的寄存器中的数据存到eax当中。

疑惑9:函数return以后,会发生什么?

b1169872235b411ea7f4149c54300143.png

由图不难看出,先是将edi、esi和ebx删除;当Add函数没必要存在时,直接将ebp指针赋给esp指针;然后把ebp寄存器(刚刚存在Add函数栈底下面的的)pop弹出,此时Add函数的函数栈帧随同ebp寄存器全都出栈了,同时ebp指针直接就回到了另一个ebp寄存器所在的位置(在main函数栈帧的下面)。

最后的ret就代表着从目前地址处弹出,即从00C21450(以本博客的例子为参考)弹回,最后回到了下图所示的main函数反汇编页面。

3ce83aff5811418b9a5a4cb893affd97.png

调用完Add函数,add esp,8 就相当于把刚刚形参变量的空间给释放了;最后把eax中存放的30给到ebp-20h所指向的栈帧区域之中。整个Add的调用结束了,同时main函数栈帧中已经存放了30这一数据。

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

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

相关文章

提升效率的AI工具集 - 轻松实现自动化

在这个快节奏、高效率的社会中&#xff0c;我们每个人都渴望能够找到提升工作效率的捷径。幸运的是&#xff0c;随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;越来越多的AI工具涌现出来&#xff0c;为我们提供了强大的支持。这些工具不仅能够帮助我们提高…

算法-分治和逆序

分治法&#xff08;Divide and Conquer&#xff09;是一种重要的算法设计范式&#xff0c;它通过将复杂的问题分解成更小、更易于管理和解决的子问题&#xff0c;然后递归地解决这些子问题&#xff0c;最后将子问题的解合并以得到原问题的解。分治法通常用于排序、搜索、数学计…

基于STM32F103C8T6单片机的DDS信号源设计

本设计能够输出三角波信号、方波信号和正弦波信号,主要由STM32F103C8T6最小核心板、电源供电电路模块、AD9833电路模块、矩阵按键电路模块、LCD1602液晶显示模块等组成。在设计中&#xff0c;使用STM32F103C8T6作为控制芯片&#xff0c;结合LCD1602液晶显示器&#xff0c;矩阵键…

稳了,搭建Docker国内源图文教程

大家好&#xff0c;之前分享了我的开源作品 Cloudflare Workers Proxy&#xff0c;它的作用是代理被屏蔽的地址&#xff0c;理论上支持代理任何被屏蔽的域名&#xff0c;使用方式也很简单&#xff0c;只需要设置环境变量 PROXY_HOSTNAME 为被屏蔽的域名&#xff0c;最后通过你的…

Unity多语言插件I2 Localization国际化应用

【就不收费了&#xff0c;要个关注不过分吧】 【图片来自插件官网&#xff0c;侵删】 前言 目前游戏往往都不会仅局限于国内语言&#xff0c;为了适应产品都要做国际化适配&#xff0c;因此会用到这个插件&#xff0c;这个插件要付费&#xff0c;因此请前往unity官网进行下载…

秒懂Linux之消息队列与信号量(了解)

目录 前言 消息队列原理 信号量理论 信号量原理 IPC资源 前言 消息队列与信息量目前已经不常用了&#xff0c;大家也可以参考共享内存去了解基本原理即可。 消息队列原理 消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法 每个数据块都被认为是有一个类型&…

mfc140u.dll引发的软件故障怎么破?mfc140u.dll文件损坏的解决办法全知道!

当这个重要的 DLL 文件丢失或损坏时&#xff0c;用户可能会收到一个错误消息&#xff0c;提示 “程序无法启动&#xff0c;因为计算机中缺失 mfc140u.dll” 或类似的提示。这种情况不仅令人困扰&#xff0c;而且可以干扰正常的工作流程&#xff0c;尤其是当您依赖特定软件完成日…

KMP算法的实现

这是C算法基础-数据结构专栏的第二十六篇文章&#xff0c;专栏详情请见此处。 引入 KMP算法是一种可以快速查找某一字符串在一个文本中的所有出现的算法。 下面我们就来讲KMP算法的实现。 定义 Knuth–Morris–Pratt 算法&#xff0c;简称KMP算法&#xff0c;是由Knuth、Pratt…

基于VUE的教师教学质量网络评测评价统计分析系统

1、 选题的背景与意义 21世纪是信息化的世纪&#xff0c;我们的一些生活习惯因为计算机而发生改变&#xff0c;我们也逐渐习惯于通过计算机的各项功能来获得便利。这其中所带来的挑战和机遇为各行业的发展指明了一个方向。教学质量评测是一项琐碎而又十分细致的工作&#xf…

【永磁同步电机(PMSM)】 3. 基于Matlab 的仿真与控制

【永磁同步电机&#xff08;PMSM&#xff09;】 3. 基于Matlab 的仿真与控制 1. 电机的仿真与控制2. BLDC 电机与 PMSM 电机3. BLDC 的方波控制4. 磁场定向控制&#xff08;FOC&#xff09;5. 空间矢量调制 (SVM)6. PMSM 模型的频率响应估计 电机仿真和控制是能源生产、汽车、航…

Java对象一口气讲完!φ(* ̄0 ̄)

Java Object类 Java面向对象设计 - Java Object类 Java在java.lang包中有一个Object类。 所有Java类都直接或间接扩展Object类。 所有Java类都是Object类的子类Object类是所有类的超类。 Object类本身没有超类。 Object类的引用变量可以保存任何类的对象的引用。 以下代…

OSPFv3协议几类LSA介绍

OSPFv3协议介绍 与OSPFv2相比&#xff0c;OSPFv3在工作机制上与OSPFv2基本相同&#xff1b;但为了支持IPv6地址格式&#xff0c;OSPFv3对OSPFv2做了一些改动。OSPFv3基于OSPFv2基本原理增强&#xff0c;是一个独立的路由协议&#xff08;v3不兼容v2&#xff09;协议号仍然是89…

竹云赋能“中国·贵州”全省统一移动应用平台建设,打造政务服务“新引擎”

近日&#xff0c;2024中国国际大数据产业博览会在贵州贵阳圆满落幕。会上&#xff0c;由贵州省政府办公厅牵头建设的“中国贵州”全省统一移动应用平台正式发布&#xff0c;聚焦民生办事、政务公开、政民互动、扁平高效、数据赋能五大模块&#xff0c;旨在打造公平普惠的服务平…

Hbase操作手册

一&#xff1a;Hbase 创建数据库表 1.进入hbase shell 2.创建数据库表的命令&#xff1a;create 表名, 列族名1,列族名2,列族名N 3.如果想查看所有数据库表&#xff0c;可以使用list 命令&#xff1a; 4.可以看到&#xff0c;刚创建的数据库表user 已经在数据库表的列表中&…

单元格左边放文字右边放按钮

1 . 代码 /* 添加到你的CSS文件中 */ .switch-td { display: flex; justify-content: space-between; /* 两端对齐&#xff0c;这样文本和开关会分别靠左和靠右 */ align-items: left; /* 垂直居中 */ } /* 如果你不想改变其他<td>的默认左对齐&#xff0c;…

TTF与图片之间的相互转换,使用python,potrace,fontforge

概述 TTF是字体文件格式&#xff0c;里面存储的是矢量化的字体信息。TTF与图片之间的相互转换简单描述如下&#xff1a; 使用python中的PIL&#xff08;pillow&#xff09;图像库可以实现TTF转图片使用potrace可以将图片转为矢量文件svg&#xff0c;再进一步使用fontforge可以…

一天认识一个硬件之连接线

我们在日常工作生活中经常会用到许多连接线&#xff0c;比如视频线&#xff0c;USB线&#xff0c;但是他们的区别在哪里&#xff0c;可能太不清楚&#xff0c;今天就来给大家分享一下。 HDMI线 特点&#xff1a;HDMI线是一种全数字化视频和声音发送接口&#xff0c;可以发送未…

phpword读取word docx文档文本及图片转html格式

最近在做一个PHP读取word文档功能&#xff0c;搜索一圈后决定选择用phpword第三方组件。 composer安装phpWord composer require phpoffice/phpword如果你的文件是doc格式&#xff0c;直接另存为一个docx就行了&#xff1b;如果你的doc文档较多&#xff0c;可以下一个批量转…

Lingo求解器基本语法

Lingo是一款用于线性规划和整数规划的数学建模和求解软件&#xff0c;被广泛应用于运筹学、生产优化、供应链管理等领域。今天与大家一起来熟悉一下它的基本语法 Lingo基本语法 1、定义目标函数为MIN&#xff0c;MAX. 2、以一个分号“&#xff1b;”结尾。除SETS,ENDSETS,D…

煤矸石检测数据集(yolo)

yolo煤矸石检测 数据集 pt模型 界面&#xff0c; ✓3091张图片和txt标签&#xff0c;标签类别两类&#xff1a;“coal”、“rock”。 ✓适用于煤矸石识别&#xff0c;深度学习&#xff0c;机器学习&#xff0c;yolov5 yolov6 yolov7 yolov8 yolov9 yolov10&#xff0c;Python 煤…