【手撕C语言 第八集】函数栈帧的创建与销毁

news2024/9/30 5:23:30

文章目录

  • 一、什么是函数栈帧?
  • 二、函数栈帧能解决什么问题呢?
    • (1)局部变量是如何创建的?
    • (2)为什么局部变量不初始化内容是随机的?
    • (3)函数调用时参数是如何传递的?传参的顺序是什么样?
    • (4)形参和实参的关系?
    • (5)函数的返回值是如何带回的?
  • 三、函数栈帧的创建与销毁解析
    • 1.什么是栈?
    • 2.认识相关寄存器和汇编指令
    • 3.解析函数栈帧的创建与销毁
      • 1.预备知识
      • 2.函数的调用堆栈
    • 4.准备环境
    • 5.转到反汇编
    • 6.函数栈帧的创建
      • 小知识:烫烫烫~
    • 7.函数栈帧的销毁
    • 8.拓展了解:
  • 四、易混乱点


一、什么是函数栈帧?

我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。

那函数是如何调用的?函数的返回值又是如何待会的?函数参数是如何传递的?这些问题都和函数栈帧有关系。

函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放:
🎗️函数参数和函数返回值
🎗️临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)保存上下文信息(包括在函数调用前后需要保持不变的寄存器)。

二、函数栈帧能解决什么问题呢?

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

首先要为局部变量所在的函数创建函数栈帧,再在函数栈帧中找到一些空间把局部变量存进去。

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

因为随机值是我们放进去的,例如:0ccccccch,初始化意味着覆盖随机值。

(3)函数调用时参数是如何传递的?传参的顺序是什么样?

在函数调用前就已经push push,把两个实参从右向左拷贝过去。在调用的函数栈帧中通过指针偏移量找到形参。
传参的顺序是从右向左。

(4)形参和实参的关系?

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

(5)函数的返回值是如何带回的?

在调用函数之前,就把call指令的下一条指令的地址存进去,把调用该函数的上一个函数栈帧的ebp存进去。当函数调用完返回的时候,将此时的ebp的值赋给esp,弹出ebp就能找到上一个函数的ebp的位置,(这个过程回收了调用的函数空间,esp,ebp此时维护main函数栈帧),由于之前记住了call指令的下一条指令的地址,当call调用完函数后,通过记住的地址找到下一条指令继续往下执行。在函数调用的过程中将函数的返回值存储到寄存器中,再通过后续的指令操作将寄存器的值放到主函数用于接收返回值的变量中。
所以函数的返回值是通过寄存器的方式带回来的。

三、函数栈帧的创建与销毁解析

1.什么是栈?

🎗️栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今看到的所有的计算机语言。
🎗️在经典的计算机科学中,栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据弹出(出栈,pop),但是栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out, FIFO)。就像叠成一叠的术,先叠上去的书在最下面,因此要最后才能取出。在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小。
🎗️在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。
🎗️在我们常见的i386或者x86-64下,栈顶由成为 esp 的寄存器进行定位的。

2.认识相关寄存器和汇编指令

相关寄存器
在这里插入图片描述
相关汇编命令
在这里插入图片描述
🎗️在这里插入图片描述

3.解析函数栈帧的创建与销毁

1.预备知识

首先我们达成一些预备知识才能有效的帮助我们理解,函数栈帧的创建和销毁。

  1. 每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。
  2. 这块空间的维护是使用了2个寄存器: esp 和 ebp , ebp 记录的是栈底的地址, esp 记录的是栈顶的地址。
    如图所示:
    在这里插入图片描述

🎗️(1)栈区的使用习惯是先使用高地址,再使用低地址
🎗️(2)空间从高地址向低地址消耗
注意
在不同的编译器下,函数栈帧的创建和销毁是略有差异的,大体逻辑是一致的,具体细节取决于编译器的实现。

2.函数的调用堆栈

演示代码

#include <stdio.h>
int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
} 
int main()
{
	int a = 3;
	int b = 5;
	int ret = 0;
	ret = Add(a, b);
	printf("%d\n", ret);
	return 0;
}

这段代码,如果我们在VS2019编译器上调试,调试进入Add函数后,我们就可以观察到函数的调用堆栈
(右击勾选【显示外部代码】),如下图:
在这里插入图片描述
🎗️函数调用堆栈是反馈函数调用逻辑的,那我们可以清晰的观察到, main 函数调用之前,是由invoke_main 函数来调用main函数。
🎗️在 invoke_main 函数之前的函数调用我们就暂时不考虑了。
🎗️那我们可以确定, invoke_main 函数应该会有自己的栈帧, main 函数和 Add 函数也会维护自己的栈帧,每个函数栈帧都有自己的 ebp 和 esp 来维护栈帧空间。

在VS2013上,我们可以清晰的观察到, main 函数调用之前,是由_ _tmainCRTStartup 函数来调用main函数,__tmainCRTStartup函数调用之前,是由mainCRTStartup 函数来调用__tmainCRTStartup函数的。
在这里插入图片描述
那接下来我们从main函数的栈帧创建开始讲解

4.准备环境

为了让我们研究函数栈帧的过程足够清晰,不要太多干扰,我们可以关闭下面的选项,让汇编代码中排除一些编译器附加的代码:
在这里插入图片描述

5.转到反汇编

调试到main函数开始执行的第一行,右击鼠标转到反汇编。
注意:VS编译器每次调试都会为程序重新分配内存,本次反汇编代码是一次调试代码过程中数据,每次调试略有差异。
在这里插入图片描述

6.函数栈帧的创建

接下来我们就一行行拆解汇编代码:
在这里插入图片描述
在这里插入图片描述
上面的这段代码最后4句,等价于下面的伪代码
在这里插入图片描述
在这里插入图片描述

小知识:烫烫烫~

在这里插入图片描述
之所以上面的程序输出“烫”这么一个奇怪的字,是因为main函数调用时,在栈区开辟的空间的其中每一个字节都被初始化为0xCC,而arr数组是一个未初始化的数组,恰好在这块空间上创建的,0xCCCC(两个连续排列的0xCC)的汉字编码就是“烫”,所以0xCCCC被当作文本就是“烫”。

接下来我们再分析main函数中的核心代码
在这里插入图片描述
在这里插入图片描述
Add函数的传参
在这里插入图片描述
在这里插入图片描述
函数调用过程
在这里插入图片描述
call 指令是要执行函数调用逻辑的,在执行call指令之前先会把call指令的下一条指令的地址进行压栈操作,这个操作是为了解决当函数调用结束后要回到call指令的下一条指令的地方,继续往后执行。
这里记住call指令的下一条指令的地址,当call调用完函数之后,通过记住的地址找到下一条指令继续执行
在这里插入图片描述
当我们跳转到Add函数,就要开始观察Add函数的反汇编代码了。
在这里插入图片描述
代码执行到Add函数的时候,就要开始创建Add函数的栈帧空间了。
在Add函数中创建栈帧的方法和在main函数中是相似的,在栈帧空间的大小上略有差异而已。

  1. 将main函数的 ebp 压栈
  2. 计算新的 ebp 和 esp
  3. 将 ebx , esi , edi 寄存器的值保存
  4. 计算求和,在计算求和的时候,我们是通过 ebp 中的地址进行偏移访问到了函数调用前压栈进去的
    参数,这就是形参访问。
  5. 将求出的和放在 eax 寄存器尊准备带回

在这里插入图片描述
图片中的 a’ 和 b’ 其实就是 Add 函数的形参 x , y 。这里的分析很好的说明了函数的传参过程,以及函数在进行值传递调用的时候,形参其实是实参的一份拷贝。对形参的修改不会影响实参。

7.函数栈帧的销毁

当函数调用要结束返回的时候,前面创建的函数栈帧也开始销毁。
那具体是怎么销毁的呢?我们看一下反汇编代码。
在这里插入图片描述
回到了call指令的下一条指令的地方:
在这里插入图片描述
但调用完Add函数,回到main函数的时候,继续往下执行,可以看到:
在这里插入图片描述

8.拓展了解:

其实返回对象时内置类型时,一般都是通过寄存器来带回返回值的,返回对象如果时较大的对象时,一般会在主调函数的栈帧中开辟一块空间,然后把这块空间的地址,隐式传递给被调函数,在被调函数中通过地址找到主调函数中预留的空间,将返回值直接保存到主调函数的。

四、易混乱点

(1)函数内部创建的静态变量是在全局开辟的;函数内部创建的局部变量是在栈区上创建的。
(2)每个函数开辟的空间不是一样大的,这取决于编译器(编译器会计算需要多大空间)
(3)寄存器独立于内存,不在内存上,是集成到CPU上的

电脑上的存储(相互独立):
硬盘
内存
寄存器

(4)为什么局部变量a和b创建的位置不是相邻的,中间隔了一块空间?
这完全取决于编译器,中间空多大空间也取决于编译器。
(5)计算机删除数据是直接允许后续操作直接覆盖的。
(6)事实上,在函数开辟空间上进行的是z=x+y操作,x,y形参空间的开辟是在main函数和add函数之间的一块空间,也可以认为是在main函数的栈帧空间里,但是不在ad函数的栈帧空间里。

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

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

相关文章

使用ffmpeg转换索尼老DV拍摄的VOB文件为mp4

一些背景故事 最近对象想用 CCD 拍照录像&#xff0c;家里刚好有一台快 20 年前的索尼 DV DCR-DVD653E&#xff0c;就是电池老化充不进去电了。 翻出来之后还感慨了一下&#xff1a;当年没有网购&#xff0c;价格不透明&#xff1b;有些地方也没有官方店&#xff0c;只有一两家…

Yuliverse:引领区块链游戏新篇章!

数据源&#xff1a;Yuliverse Dashboard 作者&#xff1a;lesleyfootprint.network 什么是 Yuliverse Yuliverse 是一款元宇宙游戏的先锋&#xff0c;是一款主打 Explore to earn 和 Social to earn 的链游。 这是一款能让你边玩边赚钱的免费区块链游戏&#xff0c;得到 LI…

解决docker desktop 登录不上账号的问题

一、背景 点击“Sign in”&#xff0c;一直卡在Verifying credentials...&#xff0c;重试也没用。 二、解决办法 1、macOS下载并安装Proxifier 2、配置Proxifier 配置Proxies 配置rule 其中的Applications填&#xff1a;"Docker.app"; "Docker"; com.…

机器学习之聚类-2D数据类别划分

无监督学习&#xff08;Unsupervised Learning&#xff09; 机器学习的一种方法&#xff0c;没有给定事先标记过的训练示例&#xff0c;自动对输入的数据进行分类或分群。 方式一&#xff1a;站着或坐着 方式二&#xff1a;全身或半身 方式三&#xff1a;蓝眼球或不是蓝眼球 …

解决TortoiseGit软件Git Show log时显示Too many files to display的问题

1 问题描述 有时代码提交修改的文件比较多&#xff0c;当查看log时无法显示出来修改的文件列表&#xff0c;如下所示&#xff1a; 2 解决方法 将LogTooManyItemsThreshold尽可能配置得大一些。 三 参考资料 https://gitlab.com/tortoisegit/tortoisegit/-/issues/3878

使用 Docker 部署 ServerStatus 服务器监控系统

一、ServerStatus 介绍 GitHub&#xff1a;https://github.com/cppla/ServerStatus ServerStatus 是一个酷炫高逼格的云探针、云监控、服务器云监控、多服务器探针~。 特性 使用 Rust 完全重写 Server、Client&#xff0c;单个执行文件部署支持上下线和简单自定义规则告警 (T…

01. eNSP环境以及VRP基本使用

eNSP的基本使用 1. eNSP的桥连接1.1. 具体操作&#xff08;1&#xff09;创建环回适配器&#xff08;2&#xff09;设置虚拟网卡&#xff08;3&#xff09;使用eNSP桥接计算机 2. 华为VRP系统2.1. 实验1&#xff1a;VRP的基本操作2.2. 实验2&#xff1a;文件命令&#xff08;1&…

mysql 基础(三)

一、多表设计 数据库设计范式 第一范式(确保每列保持原子性) 第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值&#xff0c;就说明该数据库表满足了第一范式。第二范式就是要有主键&#xff0c;要求其他字段都依赖于主键。 没有主键就没有唯一性&…

el-tree勾选后退出再打开显示之前已经勾选的

官网文档 element-ui官网文档有默认展开和默认选中 <el-tree:data"data"show-checkboxnode-key"id":default-expanded-keys"[2, 3]":default-checked-keys"[5]":props"defaultProps"> </el-tree><script>…

力扣518. 零钱兑换 II

动态规划 思路&#xff1a; 假设 dp[i] 为金额 i 使用零钱的组合数&#xff0c;其可以由其中的一种零钱 coin 和 i - coin 组合&#xff1b; 遍历零钱数组&#xff0c;对每一种零钱 coin 进行如下操作&#xff1a; 从 coin 到 amount 金额进行遍历&#xff0c;dp[j] dp[j] d…

Prometheus 薪资翻倍的监控系统?

1. 介绍与架构 Prometheus是一个开源的系统监控和警报工具包&#xff0c;用于收集和存储时间序列数据&#xff0c;包括指标信息、记录时间戳以及可选的键值对标签。许多公司使用Prometheus监控K8s集群。 2. 合适与不合适场景 合适场景 Prometheus适用于记录各种数字时间序列…

web系统架构基于springCloud的各技术栈

博主目前开发的web系统架构是基于springCloud的一套微服务架构。 使用的技术栈&#xff1a;springbootmysqlclickhousepostgresqlredisrocketMqosseurekabase-gatewayapollodockernginxvue的一套web架构。 一、springboot3.0 特性&#xff1a;Spring Boot 3.0提供了许多新特性…

每日一题——LeetCode1313.解压缩编码列表

这么简单的题目要说的这么复杂 nums里每相邻的两个元素nums[i]、nums[j]为一对&#xff0c;nums[i]表示nums[j]的次数 var decompressRLElist function(nums) {let res[]for(let i0,j1;j<nums.length-1;i2,j2){while(nums[i]--){res.push(nums[j])}}return res }; 消耗时…

代码随想录 Leetcode107. 二叉树的层序遍历 II

题目&#xff1a; 代码&#xff08;首刷自解 2024年1月24日&#xff09;&#xff1a; class Solution { public:vector<vector<int>> levelOrderBottom(TreeNode* root) {vector<vector<int>> res {};if(root nullptr) return res;queue<TreeNode…

第11次修改了可删除可持久保存的前端html备忘录:将样式分离,可以自由秒添加秒删除样式

第11次修改了可删除可持久保存的前端html备忘录&#xff1a;将样式分离&#xff0c;可以自由秒添加秒删除样式 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"…

Cesium for Unity包无法加载

太上老君急急如律⚡令⚡ &#x1f959;关闭UnityHub&#x1f9c0;启动梯子&#x1f96a;cmd 启动UnityHub &#x1f959;关闭UnityHub &#x1f9c0;启动梯子 &#x1f96a;cmd 启动UnityHub 把批处理启动文件&#x1f448;中的exe的路径换成自己的安装目录&#xff01;保存…

使用Electron打包vue文件变成exe应用程序

文章目录 一、下载Electron二、修改下载的Electron项目1.修改index.html文件2.修改main.js文件3.修改package.json文件 三、修改vue项目1.修改vite.config.js文件2.修改.env.production文件3.修改auth.js文件4.修改router下得index.js文件6.修改Navbar.vue文件 四、Electron打包…

ARMv8-AArch64 的异常处理模型详解之异常类型 Exception types

异常类型详解 Exception types 一&#xff0c; 什么是异常二&#xff0c;同步异常&#xff08;synchronous exceptions&#xff09;2.1 无效的指令和陷阱异常&#xff08;Invalid instructions and trap exceptions&#xff09;2.2 内存访问产生的异常2.3 产生异常的指令2.4 调…

Type-c一分二C+L/C+C同时快充数据线方案

一分二快充线是一种具有同时快充功能的线缆&#xff0c;可以实现同时给两个设备充电&#xff0c;并且都能达到快充的效果。那么&#xff0c;一分二快充线是如何实现同时快充功能的呢&#xff1f; 首先&#xff0c;一分二快充线采用了一种高效能的充电芯片LDR6020&#xff0c;这…

文件上传技术总结

语言可解析的后缀 &#xff08;前提&#xff1a;在Apache httpd.conf 配置文件中有特殊语言的配置 AddHandler application/x-httpd-php .php 搭配大小写、双重、空格来进行 其中&#xff1a; phtml、pht、php3、php4和php5都是Apache和php认可的php程序的文件后缀 常见的…