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

文章目录

  • 一、什么是函数栈帧?
  • 二、函数栈帧能解决什么问题呢?
    • (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.mfbz.cn/a/344605.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言第七弹---循环语句

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 循环语句 1、while循环1.1、if和while的对比1.2、while语句的执行流程1.3、while循环的实践1.4、练习 2、for循环2.1、语法形式2.2、for循环的执行流程2.3、for循…

Qt 多次绘图

使用Qt 的时候发现&#xff1a; 背景&#xff1a;自己定义一个类&#xff0c;把它和某个ui文件绑定。(类似 Qt creator 默认创建的工程&#xff09;问题&#xff1a;当鼠标在窗口内单击的时候会触发2次绘图。&#xff1f;难道不应该是一次吗&#xff1f; 于是开始了如下的测试…

linux安装python3.11

yum -y install gcc-c zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel libffi-devel下载地址 https://www.python.org/ftp/python/3.11.7/Python-3.11.7.tar.xz 上传python文件&…

uniapp+vue3+ts--编写微信小程序对接e签宝签署时跳转刷脸效果(人脸识别)中间页代码

uniappvue3ts–编写微信小程序对接e签宝签署时跳转刷脸效果&#xff08;人脸识别&#xff09;中间页代码 e签宝内嵌H5方式集成签署页的文档说明&#xff1a;https://open.esign.cn/doc/opendoc/case3/ahb0sg 签署时跳转刷脸效果示意图&#xff1a; 1. 在文件夹新建一个文件&a…

GitHub提交 / 拉取时 443 fatal: unable to access ‘https:

这个问题嘛 懂得都懂 但是用了魔法后依旧会出现443错误 排查了工具发现并不是工具的问题 修改一下git代理即可解决 解决方法如下 确保魔法可用的情况下 打开魔法 打开系统设置 > 网络和Internet > 代理 找到自己的代理IP 如下 这里以我的代理IP和端口举例 在…

黑马程序员JavaWeb开发|Maven高级

一、分模块设计与开发 分模块设计&#xff1a; 将项目按照功能拆分成若干个子模块&#xff0c;方便项目的管理维护、扩展&#xff0c;也方便模块间的相互调用&#xff0c;资源共享。 注意&#xff1a;分模块开发需要先对模块功能进行设计&#xff0c;再进行编码。不会先将工…

使用ElEment组件实现vue表单校验空值

1.绑定表单组件数组rules 2.在data域中设定组件rules 3.设定调用方法函数 提交校验 取消&#xff1a; 测试页面 提交空值 失去焦点 取消重置 提交后重置

完美解决:“已损坏,无法打开。 您应该将它移到废纸篓。”

1、Mac为什么会出现这个问题&#xff0c;懂得都懂&#xff08;/dogo&#xff09;。 2、首先看一下系统中的安全性与隐私设置&#xff0c;是否选择了任意来源&#xff0c;如果没有解锁设置。 3、如果还是报错&#xff0c;就可以祭出大招了&#xff0c;给文件赋予安全性设置。在…

AI分割一切模型SAM(Segment Anything Model)的C++部署

2023年最火爆的分割模型莫过于SAM&#xff0c;截止今天2024年1月19日&#xff0c;github上的star已经达到了41.7k的惊人数量。下面我们来体会一下如何运行这个模型&#xff0c;以及如何用C部署这个模型。 检查cuda环境 我的Cuda版本是12.0.1&#xff0c;如下&#xff0c; Cudn…

03.Elasticsearch应用(三)

Elasticsearch应用&#xff08;三&#xff09; 1.核心概念介绍 注意&#xff1a;类型&#xff08;Type&#xff09; 6.0之前的版本有Type概念&#xff0c;type相当于关系型数据库的表&#xff0c;ES官方将在ES9版本中彻底删除Type。7里面Type为ES默认的类型_doc 2.Cat API 介…

Matlab|基于改进遗传算法的储能选址定容(可任意设定储能数量)

目录 主要内容 部分代码 结果一览&#xff08;以3个储能为例&#xff09; 下载链接 主要内容 该模型采用改进遗传算法优化配电网系统中储能选址位置和容量&#xff0c;程序以IEEE33节点系统为分析对象&#xff0c;以网损最小为目标&#xff0c;采用matpower实现系…

diffusion入门

1. diffusion model 概念 https://zhuanlan.zhihu.com/p/638442430 这篇博客写得很好&#xff0c;顺便做一点笔记记录一下。 原博客附带的代码也很清晰易懂。 1.1 前向过程 后一个过程等于前一个结果的均值乘上sqrt(1-beta_t), 再加上方差beta_t的噪声。 这样下去可以得到 x…

每日一道算法题 16(2023-12-29)

package com.tarena.test.B20; import java.util.Arrays; import java.util.Scanner; /** * * 题目描述&#xff1a; 输入一个由n个大小写字母组成的字符串&#xff0c;按照Ascii码从小到大的排序规则&#xff0c;查找字符串中第k个最小ascii码值的字母&#xff08;k>…

023-安全开发-PHP应用后台模SessionCookieToken身份验证唯一性

023-安全开发-PHP应用&后台模&Session&Cookie&Token&身份验证&唯一性 #知识点&#xff1a; 1、PHP后台身份验证模块实现 2、Cookie&Session技术&差异 3、Token数据包唯一性应用场景 项目1&#xff1a;用cookie做后台身份验证 项目2&#xff1a…

git bash右键菜单失效解决方法

git bash右键菜单失效解决方法 这几天重新更新了git&#xff0c;直接安装新版本后&#xff0c;右键菜单失效找不到了。找了好几个博客&#xff0c;发现都不全面&#xff0c;最后总结一下解决方法&#xff1a; &#xff08;1&#xff09;按winr&#xff0c;输入regedit打开注册…

解决git管理GitHub连接问题

前言 git提交文档到GitHub老是出问题&#xff0c;记录下 报错 首先是常规操作更新文档&#xff0c;命令如下 $ git add . $ git commit -m "add" $ git push origin main后面老是报这种错误&#xff0c;如下图 To github.com:zhenxijiabei/yuque.git! [rejected…

mysql-进阶篇

文章目录 存储引擎MySQL体系结构相关操作 存储引擎特点InnoDBInnoDB 逻辑存储结构 MyISAMMemory三个存储引擎之间的区别存储引擎的选择 索引1. 索引结构B-TreeB-Tree (多路平衡查找树)B-Tree演变过程 BTree与 B-Tree 的区别BTree演变过程 Hash 2.索引分类3.索引语法演示 4.SQL性…

react18介绍

改进已有属性&#xff0c;如自动批量处理【setState】、改进Suspense、组件返回undefined不再报错等 支持Concurrent模式&#xff0c;带来新的API&#xff0c;如useTransition、useDeferredValue等 如何升级React 18 npm install reactlatest react-domlatestnpm install ty…

5分钟做自己的微信红包封面

文章目录 怎么制作自己的红包封面&#xff1f;开通红包封面的要求如下&#xff1a;收费情况制作具体网站&#xff1a;https://chatapi.onechat.fun/register?affYoU6 提交审核logo封面、挂件、气泡证明材料 发放红包封面其他 怎么制作自己的红包封面&#xff1f; 开通红包封面…

OpenCV书签 #结构相似性SSIM算法的原理与图片相似性实验

1. 介绍 结构相似性&#xff08;Structural Similarity&#xff0c;简称SSIM算法&#xff09;&#xff0c;主要用于检测两张相同尺寸的图像的相似度、或者检测图像的失真程度&#xff0c;是一种衡量两幅图像相似度的指标。 定义 给定两个图像 x 和 y&#xff0c;两张图像的结…
最新文章