函数栈帧的创建与销毁

news2024/11/24 9:21:03

在这里插入图片描述

  • 魔王的介绍:😶‍🌫️一名双非本科大一小白。
  • 魔王的目标:🤯努力赶上周围卷王的脚步。
  • 魔王的主页:🔥🔥🔥大魔王.🔥🔥🔥
    在这里插入图片描述
    ❤️‍🔥大魔王与你分享:莫泊桑说过,生活可能不像你想象的那么好,但是也不会像你想象的那么糟。人的脆弱和坚强都超乎了自己的想象。有时候可能脆弱的一句话就泪流满面,有时候你发现自己咬着牙已经走过了很长的路。

文章目录

  • 一、前言
  • 二、问题举例
  • 三、介绍
    • 1.寄存器
    • 2.汇编指令
  • 四、讲解
    • 1.main函数并不是最终函数
    • 2.函数栈帧的创建
    • 3.函数调用
      • 开辟空间前执行的操作:
      • 开辟空间及销毁前的操作:
      • 销毁操作:
    • 4.调用函数后的汇编指令:
  • 五、总结

一、前言

我们在编译代码时会有很多不清楚的地方,例如我们创建变量时我们只知道会开辟空间,却不知道要在哪开辟,怎么开辟,在我们调用函数时,我们也知道要在栈区开辟空间,但是依然不知道怎么开辟,参数如何拷贝,为什么值传递不会改变原数值。本篇博客带你理解栈区(局部变量和函数调用开辟空间的地方)是怎样工作的。

二、问题举例

  • 局部变量是怎么创建的?
  • 为什么局部变量的值是随机值?
  • 函数时怎么传参的?传参的顺序是怎样的?
  • 形参和实参是什么关系?
  • 函数调用时怎么做的?
  • 函数调用时结束后怎样返回的?
  • 通过下面的讲解你将全部明白这些问题

三、介绍

  • 首先说明一些比较陌生的东西,以便后续的理解。

1.寄存器

  1. 寄存器:中央处理器内的组成部分。寄存器是有限存贮量的高速存贮部件,它们可以来暂存指令、数据和地址。寄存器是独立于内存的,不会因为栈区函数空间的销毁而销毁。
  2. 寄存器举例:
    eax、ebx、ecx、edx、ebp、esp等
    其中三个寄存器比较重要:
    1.eax:接收函数返回值,使得函数的返回值不会因为栈区的销毁而消失。
    2.ebp:栈底寄存器(高地址),和esp共同维护栈区新开辟的空间。
    3.esp:栈顶寄存器(低地址),和ebp共同维护栈区新开辟的空间。

2.汇编指令

push:压栈,放入栈顶一个元素(寄存器的内容,并不是寄存器)。
pop:出栈,把栈顶数据弹出,并把弹出的元素赋值到寄存器中。
move:给一个寄存器赋值。
sub:减法指令。
add:加法指令。
lea:即load effective address,加载有效地址。
call:调用函数,并且把该函数之后的地址进行压栈(目的是为了在函数销毁后可以回到函数之后的下一步)。
dword:即double word,可以理解为两倍的字的大小,我们知道一个汉字两个字节,那么两倍的就是四个字节。
rep stors:重复拷贝寄存器中的数据。
注意:esp永远指向栈顶,每次压栈后esp就会自动减去4个字节(因为栈区的使用是高地址向低地址使用),每次出栈后esp就会加上4个字节。

四、讲解

  • 以此代码为标准进行详细说明:
#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;
}

1.main函数并不是最终函数

我们一直认为main函数结束,程序就会结束,main函数是程序的最后一个函数,你是否想过这样一个问题:为什么main函数会有返回值呢?那自然是因为main函数并不是最终函数,main函数也是被其他函数所调用的。我们转到调用堆栈就会观察到,如图:

在这里插入图片描述

  • 那么到底是什么函数调用的main函数呢?
    在这里插入图片描述

是__tmainCRTStartup函数调用的main函数。__tmainCRTStartup函数又是被mainCRTStartup函数调用的。

  • 也就是main函数后其实还有两个函数,如图:在这里插入图片描述

所画的图只表示调用顺序和栈帧使用顺序(先使用高地址在使用低地址)。

2.函数栈帧的创建

函数栈帧怎样创建,我们可以通过反汇编观察得到。要进入反汇编,首先要进入调试,然后按照图片操作即可进入,如图:

在这里插入图片描述

  • main函数的栈帧创建

函数栈帧创建的逻辑基本一样,所以详细说一个怎么创建,其他就基本也是这样,这里以主函数举例。通过图一点点进行分析,如图:在这里插入图片描述

所画的15个指令是在Add函数之前的汇编指令,前10个指令是创建主函数的栈帧空间并赋值为CCCCCCCC,这就是当我们不给一个变量赋值时,会打印出烫烫烫烫的原因。第11、12这两个指令是新增的,在VS2013上并没有,所以无视就行。那么从第13个指令开始,就该使用所开辟好的空间了。详细如下:

  1. 用ebp的值进行压栈。然后esp-4(没个元素都是4个字节,栈区的使用是从高地址向低地址使用的)。
  2. 把esp的值赋给ebp。
  3. 让esp减去0E4h这个十六进制数字。(目的是给main函数开辟空间,esp是维护栈顶的,所以要让esp移动到新开辟的栈顶位置)
  4. 用ebx的值进行压栈。
  5. 用esi的值进行压栈。
  6. 用edi的值进行压栈。
  7. load effective address,加载有效地址。放入edi中。
  8. 把9赋值到ecx中。
  9. 把CCCCCCCC赋值到eax中。
  10. 重复拷贝:从edi(加载的那个有效地址)开始,拷贝ecx次(9),每次拷贝eax(CCCCCCCC),每次拷贝的大小为dword(4字节)。每拷贝一次,edi(存放有效地址的这个寄存器都加4),ecx(存放拷贝次数的寄存器都减1),直到ecx减到0,停止拷贝,执行下一个汇编指令。
    前十个是为main函数开辟空间并赋值为CCCCCCCC的汇编码。接下来才开始让我们写的程序分配空间。
  11. 在ebp-8位置处赋值0Ah(也就是10).
  12. 在ebp-14h(ebp-20)位置处赋值14h(也就是20)
  13. 在ebp-20h(ebp-32)位置处赋值0
  • 如图:
    在这里插入图片描述

第一个图其实__tmainCRTStartup函数是被mainCRTStartup函数调用的,不过这里就不画了,我们主要是理解main函数及main函数内的函数调用如何在栈区开辟空间就好了,main函数之前的那两个函数知道有就行了。

这个图如果在VS2013是正确的,开辟的空间全部先初始化,但是在VS2022中,其实开辟的主函数的空间并没有全部赋值,如图:

在这里插入图片描述

  • 最后一次拷贝后的内存展示:在这里插入图片描述

3.函数调用

开辟空间前执行的操作:

  • 函数调用的时候,我们都知道会开辟空间,但是他是怎么开辟的呢?
  • 我们知道形参是实参的临时拷贝,那么它是怎样拷贝的呢?
  • 我们知道函数结束后该函数所处的栈帧空间就要被销毁回收,那么如果该函数有返回值,明明已经被销毁了,它是怎样再返回去的呢?
    接着往下看,你就会全部知道。
  • 在调用函数这个操作中,刚开始进行的是让实参(实参地址中的内容那就是实参的数值)赋给eax,ecx,从右向左压栈,也就是先压栈b的值,再压栈a的值,这就是反汇编的前四行。其实这四行目的就是拷贝实参。第五行call指令是调用Add这个函数并且把调用Add函数后的下一个操作指令进行压栈,目的是为了在调用函数结束后,可以回到Add函数之后的下一步指令。
    在这里插入图片描述

开辟空间及销毁前的操作:

  • 汇编指令如图:在这里插入图片描述
    在这里插入图片描述

红色框不含黄色部分:
还是和之前main函数开辟一样,先是压栈ebp,然后让ebp移动到esp位置,然后esp减去一个数值(即开辟的空间大小),然后进行(部分)(不同编译器可能不一样,有可能使部分赋值,有可能是整个空间赋值)赋值,赋值为CCCCCCCC,然后就是为Add函数中的变量分配空间(如下图)。
红色框中的黄色部分:
这个部分是关于函数返回值的汇编指令,两个图结合看,ebp+8的值赋给eax(是寄存器,独立于内存,不会因为栈帧空间销毁而销毁),然后让eax的值加上ebp+0Ch这个地址的值,这个地址转换为十进制也就是ebp+12,再让eax的值赋给该函数中的变量z,所以函数的返回值也就暂时储存在了寄存器eax中(因此当z被销毁后,eax里还存着函数的返回值,等待之后的指令),那么你是否发现了,ebp+8其实就是在开辟函数空间前拷贝的10,ebp+12就是开辟空间前拷贝的20,所以eax就变成了30,也就是说函数使用的实参其实在自己内部空间根本就没有,他们只是通过地址访问了在开辟函数空间前压栈的那些形参,仅仅是使用而已,因此值传递不改变原数值。

销毁操作:

在这里插入图片描述

  • 按照老编译器(老编译器中没有划掉的那三行)来说:
  • 第1步:把z的值赋给eax,因为z要销毁了,但是返回值要被保留,所以要借助寄存器保留下来。
  • 2~4步:pop指令,也就是出栈,最上面的三个元素,并把元素的值分别赋给edi、esi、ebx,其实这三个pop指令中的赋值操作没用,因为弹出的是edi,edi里边本来存的就是edi的值。其他两个也是这样。但是对于倒数第二行的pop指令中的赋值操作就用处很大了,接着看,等会会说到(第6步)。
  • 第5步:把ebp的值赋给esp,那就是说esp离开了正在维护的函数的栈帧空间,那么这一部分就让函数栈帧空间被销毁了。
  • 第6步:出栈,并且把出栈的值(是ebp指向main函数底部时压栈上去的那个地址,也就是说出栈的这个元素其实存放的是main函数的栈底指针)赋给寄存器ebp。那么ebp就回到原来的位置了。(main函数的栈底指针处)
  • 第七步:ret,即返回,这一步也很重要,它的意义是弹出并回到弹出的这个地址所指向的指令,因为第六步我们只是让ebp回到了main的栈底位置,此时esp和ebp位置都就绪了,但是指令却还没有返回,而这一步其实就是弹出了调用函数之后的那个指令的地址(之前压栈上去的那个),并且回到弹出这个指令的位置,也就是调用函数的下一个指令的位置,那么一切便结束了,后面就是调用函数之后的操作了。

4.调用函数后的汇编指令:

  • 最后两行指令:
    在这里插入图片描述
  • 目前的栈帧图:
    在这里插入图片描述

第一个指令esp加8,那么就是说esp跳过了临时拷贝的变量,也就是销毁了临时拷贝形参的栈帧空间。
第二个指令就是把eax寄存器中存的函数的返回值赋到main函数中接收Add函数返回值的地址中,也就是变量c的位置,往上翻看看前面你就会发现变量c的地址就是eax要赋的地址处。

  • 那么相信你看完函数栈帧的创建于销毁后对栈帧空间的运行原理有了更深层次的理解。

五、总结

在这里插入图片描述

✨请点击下面进入主页关注大魔王
如果感觉对你有用的话,就点我进入主页关注我吧!

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

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

相关文章

【数据结构与算法】基于回溯算法实现八皇后问题

八皇后问题是一个经典的计算机科学问题&#xff0c;它的目标是将8个皇后放置在一个大小为88的棋盘上&#xff0c;使得每个皇后都不会攻击到其他的皇后。皇后可以攻击同一行、同一列和同一对角线上的棋子。 一、八皇后问题介绍 八皇后问题最早由国际西洋棋大师马克斯贝瑟尔在18…

Pandas入门实践3 -数据可视化

人类大脑擅长于在数据的视觉表现中寻找模式;因此在这一节中&#xff0c;我们将学习如何使用pandas沿着Matplotlib和Seaborn库来可视化数据&#xff0c;以获得更多的特性。我们将创建各种可视化&#xff0c;帮助我们更好地理解数据。 使用pandas绘图 我们可以使用plot()方法创…

网络安全之防病毒网关

目录 网络安全之防病毒网关 恶意软件 按照传播方式分类 病毒 蠕虫 木马 按照功能分类 后门 勒索 挖矿 恶意代码的特征 下载特征 后门特征 信息收集特征 自身感染特性 文件感染特性 网络攻击特性 病毒威胁场景 病毒传播途径 电子信息 网络共享 P2P 系统漏洞 广…

电压有效值电容和电感的电压电流相位关系以及电抗和容抗值推导

注意下面所有www表示的都是角速度而不是频率 电压有效值 高中物理中知道有效值电压是根据电阻发热的功率等效得到的 对于正弦波的电压&#xff0c;UUmsinwtUU_{m}sinwtUUm​sinwt,对应的电流IUmRsinwtI\frac{U_{m}}{R}sinwtIRUm​​sinwt 求得一个周期的发热量 ∫0TI2Rdt∫0T…

5分钟告诉你如何成为一名黑客?从萌新成为大佬,只需掌握这5点(思维、编程语言、网络安全、入侵实操、法律)

说到黑客&#xff0c;大家脑海里是不是都已经显现了他的模样 仅用一台电脑 就能黑手机 黑银行卡、 黑摄像头、 让 ATM 疯狂吐钞&#xff0c; 真的是太酷了… 试问谁还能没有个黑客梦呢&#xff1f; 本篇文章&#xff0c;小编就是要带大家揭秘黑客的神秘面纱&#xff0c;…

【Cisco Packet Tracer| 二.telnet方式远程登录交换机】

文章目录一.PC0通过console线连接交换机二.PC1通过Telnet远程登录交换机1.PC1通过双绞线连接交换机2.给主机设置IP地址3.给交换机配置一个虚拟的管理接口4.全局模式下设置交换机进入特权模式的密码5.设置5个虚拟终端用户6.测试6.1测试主机和交换机是否在同一个网段中6.2主机远程…

基于Tensorflow搭建卷积神经网络CNN(花卉识别)保姆及级教程

项目介绍 TensorFlow2.X 搭建卷积神经网络&#xff08;CNN&#xff09;&#xff0c;实现人脸识别&#xff08;可以识别自己的人脸哦&#xff01;&#xff09;。搭建的卷积神经网络是类似VGG的结构(卷积层与池化层反复堆叠&#xff0c;然后经过全连接层&#xff0c;最后用softm…

Vulnhub:Digitalworld.local (Mercy v2)靶机

kali&#xff1a;192.168.111.111 靶机&#xff1a;192.168.111.130 信息收集 端口扫描 nmap -A -v -sV -T5 -p- --scripthttp-enum 192.168.111.130 使用enum4linux对目标smb服务进行枚举 enum4linux -a 192.168.111.130 目标文件共享的目录 目标存在的用户 8080端口的网…

电阻器的原理、类型、参数以及生活中常见的应用

电阻器是电子电路中最基本的元件之一&#xff0c;它的作用是限制电流流过的大小&#xff0c;在电子电路中广泛应用于电流控制、电压分压、信号衰减等方面。在本文中&#xff0c;我们将详细介绍电阻器的原理、类型、参数以及生活中常见的应用。 一、电阻器的原理 电阻器是一种…

【Docker】Docker复杂安装(mysql+redis)

安装mysql主从复制 主从复制原理 主从搭建步骤 新建主服务器容器实例3307 [root192 ~]# docker run -d -p 3307:3306 --privilegedtrue -v /tmp/mysql-master/log:/var/log/mysql -v /tmp/mysql-master/data:/var/lib/mysql -v /tmp/mysql-master/conf:/etc/mysql -e MYS…

魔兽世界巫妖王架设教程-娱乐版

相信各位拿到一个优秀的魔兽端&#xff0c;在单机把玩一番之后&#xff0c;肯定都想着能不能假设一个外网服务器&#xff0c;然后让朋友们来到自己的服务器上玩耍&#xff0c;自己还能体会一下在众多凡人面前当神&#xff08;GM&#xff09;的乐趣。网上这方面的教程有一些&…

【嵌入式环境下linux内核及驱动学习笔记-(3-字符设备驱动详解)】

目录1、文件系统与设备驱动2、设备文件2.1 linux的文件种类&#xff1a;2.2 设备分类3、 设备号3.1 dev_t类型3.2 与设备号相关的操作介绍3.2.1 宏 MKDEV3.2.2 宏 MAJOR3.2.3 宏 MINOR3.2.4 命令mknod3.2.5 register_chrdev_region()3.2.6 alloc_chrdev_region()3.2.7 unregist…

【剑指offer-C++】JZ82:二叉树中和为某一值的路径(一)

【剑指offer-C】JZ82&#xff1a;二叉树中和为某一值的路径[一]题目描述解题思路题目描述 描述&#xff1a;给定一个二叉树root和一个值 sum &#xff0c;判断是否有从根节点到叶子节点的节点值之和等于 sum 的路径。 1.该题路径定义为从树的根结点开始往下一直到叶子结点所经…

一篇文章 学会 Vue3 极速入门 (附带增删改查 案例 + Springboot)

vue3前置 00-导学 这将是你能看到的最快速Vue3 入门文章&#xff0c; 我们将快速的 去学习Vue3相关的知识&#xff0c;并结合后端做一个增删改查的项目&#xff0c;能够帮助你快速的上手Vue3&#xff0c; 包含了Vue 所含的所有特性&#xff0c; 你会知道 Vue3和Vue2 的区别&am…

Day944.度量指标 -系统重构实战

度量指标 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于度量指标的内容。 很多时候在研发过程中&#xff0c;都习惯性地用“拍脑袋”的方式来看待一个事情。例如这个代码写得不好、这个自动化测试覆盖不充分、版本的发布频率太差了等等。往往只知道哪里有问题&…

后台服务异常?测试右移告警监控早知道。。。。

目录 引言 “测试右移”思想下实践步骤 什么是“测试右移” 一、收到问题反馈 二、沟通定位问题 1.服务架构 三、讨论并选定解决方案 1.讨论分析解决方案 2.选定解决方案 四、解决方案实现 1.总体方案设计 2.编写监控脚本 3.配置服务器定时任务 五、测试环境验证…

从零开始学Java之Integer底层原理探究

前言 在之前的两篇文章中&#xff0c;壹哥给大家介绍了Java中的包装类及其特点、用法&#xff0c;但是这些内容主要是停留在”怎么用“的层面&#xff0c;没有太多涉及”为什么“&#xff0c;所以接下来壹哥会给大家讲一讲Integer这个包装类的底层原理。在现在的就业环境下&am…

Apache Tomcat CVE-2020-1938 漏洞

Apache Tomcat CVE-2020-1938 漏洞简单复现 文章目录 Apache Tomcat CVE-2020-1938 漏洞简单复现实验准备实验步骤搭建环境nmap扫描漏洞端口POC代码验证漏洞修复建议 参考链接 实验准备 所选漏洞&#xff1a;Apache Tomcat远程代码执行漏洞 漏洞编号&#xff1a;CVE-2020-193…

AppArmor零知识学习三、源码介绍与下载

本文内容参考&#xff1a; AppArmor配置&#xff08;二&#xff09;_domybest_nsg的博客-CSDN博客&#xff0c; Apparmor简单学习_trap0D的博客-CSDN博客&#xff0c; 学习LSM(Linux security module)之三:Apparmor的前世今生和基本使用_wx5b7658e51ef04的技术博客_51CTO博客…

JavaEE初阶学习:文件操作

1.文件 1.认识文件 平时说的文件一般都是指存储再硬盘上的普通文件&#xff0c;形如txt&#xff0c;jpg&#xff0c;MP4&#xff0c;rar等这些文件都可以认为是普通文件&#xff0c;它们都是再硬盘上存储的。 在计算机中&#xff0c;文件可能是一个广义的概念&#xff0c;就…