函数栈帧的创建和销毁

2023-11-06

前言

前面在使用函数时一直说到函数栈帧的创建与销毁,但也只是云里雾里的,今天就来讲讲关于函数栈帧的知识。实验环境:VS2013,系统环境X86

我们通过一段简单的代码来了解函数的栈帧:

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

寄存器

再了解函数栈帧前,我们需要先知道一些前景知识,首先来了解一下寄存器。再VS2013中,我们可以通过调试窗口中的寄存器窗口查看有哪些寄存器:

在这里插入图片描述

可以看到有下面几种寄存器:

通用寄存器(数据存放数据使用):
eax
ebx
ecx
edx

变址寄存器(偏移量):
esi
edi

指令寄存器(下一条指令的地址):
eip

指针寄存器(地址,维护函数栈帧):
esp(堆栈指针寄存器,用于存放栈顶指针的位置)
ebp(基址指针寄存器,用于寻找栈内的元素)

标志性寄存器(不知道什么东西):
efl

寄存器的详细知识可以点这里:汇编——寄存器的分类和功能

main函数的调用

每一个函数的调用都需要在栈区上开辟一块开空间,在栈上一块专门为函数开辟的空间就是函数的栈帧。这么一块栈帧其实是由两个寄存器维护的,根据上面寄存器的介绍,大概能猜到是esp, ebp两个寄存器维护的了:

在这里插入图片描述

调用main函数的函数

main函数也是函数,所以我们可以在调试中通过函数调用堆栈来看一下main函数是由谁调用的:

在这里插入图片描述

由此我们可以很明显得看到main函数的调用关系

在这里插入图片描述

调用main函数的函数__tmainCRTStartup也是需要栈帧的,同样的是由esp, ebp来维护

在这里插入图片描述

main函数的栈帧如何开辟的

然后我们可以通过反汇编来看一下main函数是怎么调用的:

在这里插入图片描述


push(保存调用方的ebp

当执行第一个反汇编指令时相当于将ebp的值放到__tmainCRTStartup的栈帧的顶部:

在这里插入图片描述

那么esp的值,就相当于减去了 4 ,我们可以通过监视来看一下

执行前:

在这里插入图片描述

执行完push指令后:

在这里插入图片描述

至于为什么是减4,是因为在32为系统下指针的大小是4字节

这样之后的函数返回后就可以快速找到调用方的栈底,从而继续维护调用方。


move(维护新开栈帧的栈底)

move指令相当于将 esp的值复制给ebp

在这里插入图片描述

通过监视窗口可以看到ebp值的变化:

执行前:

在这里插入图片描述

执行完move指令后:

在这里插入图片描述

这样ebp 就是新的函数栈帧的栈底,继续干着栈底指针的老本行

sub(维护新开栈帧的栈顶)

执行sub命令相当于esp向下走了 0E4h,那么esp ~ ebp中间的空间就是main函数的栈帧。

执行前:

在这里插入图片描述

执行完sub命令后:

在这里插入图片描述

相当于:

在这里插入图片描述

三连push(添加栈帧的信息的变量)

push前:

在这里插入图片描述

push 后:

在这里插入图片描述

注意每次压栈后都esp的值都会变化

相当于:

在这里插入图片描述

这三个新压栈的寄存器在后面将会起到大作用。

lea (存放栈顶地址)

在这里插入图片描述

lea ,全称 load effictive address,翻译一下就是加载有效地址。可以看到ebp - 0E4h 就是三连 push前main函数的栈顶,相当于将该地址放到 edi 中。

后面两个move等价于:ecx = 39h, eax = 0CCCCCCCCh

rep stos(初始化栈帧)

执行前:

在这里插入图片描述

dword

d: double
word: 一个word是2字节
dword: 4字节

整条指令就是相当于将edi (ebp - 0E4h)往下,每次操作4字节,操作 ecx (39h)次,全部初始化为 eax (0xcccccccc)

在这里插入图片描述

也就是:

在这里插入图片描述

至此,一个函数的栈帧的创建就完成了,这也是为什么局部变量没有初始化的时候,它的值会是随机数。

add函数的执行

创建变量

函数栈帧创建完成之后就是正常的执行代码了,后面三条语句就是普通的初始化

在这里插入图片描述

赋值后:

在这里插入图片描述

也就是相当于:

在这里插入图片描述

可以看到在VS2013 中,是隔了两个整型的大小来进行初始化的,但是在不同的编译器下可能实现的方式不同。

传参

接下来就是万众瞩目的传参的过程了:

在这里插入图片描述

将a和b的值依次传给eax和ecx压栈

也就是:

在这里插入图片描述

正好印证了形参是实参的一份临时拷贝

call (函数调用 )

执行完传参后,就是函数调用了,call 指令会将下一条指令压栈,让函数返回时,正常往下执行

在这里插入图片描述

然后跳到指定的地址

在这里插入图片描述

再通过jmp 命令进行跳到add函数内部

在这里插入图片描述

跳到add函数内部之后,就开始函数的创建和初始化等一系列操作,

在这里插入图片描述

参数的使用

参数使用时通过ebp找到对应的参数,然后将计算的结果返回回去

在这里插入图片描述

也就是:

在这里插入图片描述

可以发现参数在传递的时候是从右向左传,在使用形参时是从左向右取,刚好跟参数传递的顺序一致

函数的返回值和栈帧的销毁

返回时,先将返回值放到寄存器 eax

在这里插入图片描述

然后依次销毁函数栈帧,现将顶上的三个寄存器弹出

在这里插入图片描述

也就是:

在这里插入图片描述

然后通过move指令将栈顶指向栈底

在这里插入图片描述

也就是:

在这里插入图片描述

然后通过pop命令将ebp指向main函数的栈底,那么ebp, esp又重新开始维护main函数的栈帧。

在这里插入图片描述

也就是:

在这里插入图片描述

可以看到esp 指向的就是调用函数之后的下一条指令的地址,ret指令就是通过pop回到调用函数的下一条指令

在这里插入图片描述

执行ret指令后回到下一条指令的位置:

在这里插入图片描述

esp也向下走了一个位置:

在这里插入图片描述

形参的销毁

在这里插入图片描述

执行该命令后,形参的栈帧也就销毁了,意味着返回到了main函数中,add函数的栈帧彻底销毁了

在这里插入图片描述

之后的就是正常的进行计算等等,各种函数的调用,其栈帧的创建和销毁基本上都是一样的。

总结

自己总结的草图:栈帧的创建和销毁都是一一对应的,怎么创建,就怎么反着销毁。

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

函数栈帧的创建和销毁 的相关文章

  • BLE芯片PHY6222的ANCS代码解读

    BLE芯片PHY6222的ANCS代码解读 ANCS是什么 实现原理 PHY6222软件实现框架 要获取的详细信息 开通知源 通知源的解析 数据源的解析 ANCS是什么 ANCS 苹果通知中心 Apple Notification Cent
  • 对聚合函数(sum,count,min,max,avg)和having的作用和理解

    having诞生 mysql中 当我们用到聚合函数 如sum count后 又需要筛选条件时 having就派上用场了 因为WHERE是在聚合前筛选记录的 having和group by是组合着用的 下面通过实例介绍下用法 例如 selec
  • PS怎么把图片处理的更清晰

    视频没有 把步骤都写了出来 看下对你有帮助不 方法一 1 复制图层 2 去色 3 滤镜 其它 高反差保留 4 叠加 比较简单 但是效果没方法二的好 方法二 1 打开一张模糊的照片 2 选择通道 红色通道 3 复制红色通道 4 执行滤镜 风格
  • python如何实现GRPC服务,python实现简单的grpc通信

    引流个人主页 尚拙谨言的博客 CSDN博客 技术实战 学习经验分享 大道至简系列领域博主 grpc是一种基于某种协议实现不同机器间进行通信的服务框架 不同机器可以是不同的服务端 客户端 当服务端实现好某些功能后 提供一个服务接口 供不同客户
  • 用qDebug输出

    1 像printf 那样输出 a A 读入一个浮点值 仅C99有效 c 读入一个字符 d 读入十进制整数 i 读入十进制 八进制 十六进制整数 o 读入八进制整数 x X 读入十六进制整数 s 读入一个字符串 遇空格 制表符或换行符结束 f

随机推荐

  • 每日一道面试题之什么是C/S架构?什么是B/S架构?

    C S架构 Client Server架构 是一种分布式计算架构 其中客户端应用程序与服务器应用程序之间通过网络进行通信 在C S架构中 客户端负责用户界面和交互 而服务器负责处理业务逻辑和数据存储 例如 我们经常使用的数据库管理系统 如M
  • linux脚本解释,shell 脚本中的注释详解

    上次写了shell脚本的注释 没想到那么多人的需要 也存在不少不足 这次做个补充吧 单行注释 单行注释就比较简单了 直接在行最前端加上符号 即可 具体用法如下所示 this is comment test echo this is comm
  • Sass运算

    1 加法 加法运算是 Sass 中运算中的一种 在变量或属性中都可以做加法运算 如 box width 20px 8in 编译出来的 CSS box width 788px 但对于携带不同类型的单位时 在 Sass 中计算会报错 如下例所示
  • module 'tensorflow' has no attribute 'random_normal'

    报错 module tensorflow has no attribute random normal 说明tensorflow中没有random normal这个方法 最新一版的random normal方法已经换为 random nor
  • Python编程语言概述

    Python编程语言概述 Python是一种高级编程语言 以其简洁 易读和可扩展性而闻名 它具有广泛的应用领域 包括Web开发 科学计算 人工智能和数据分析等 本文将介绍Python的基本特性 语法结构和一些常用的编程范例 Python的基
  • makefile后缀规则

    linux下采用c 编写程序后编译成可执行文件时 敲打的命令太多 尤其是对于同时编译很多文件时尤其不便 采用后缀规则可以节省很多功夫 下面是一个简略的makefile文件 只需敲入make 源码文件名 out即可完成编译 继续学习中 CPP
  • 冒泡排序_C++

    include
  • Hadoop之CDH安装

    1 离线数据存储及查询环境部署 离线数据的存储与查询主要是以hadoop为中心的技术栈 包括hive hbase hue kylin等 部署hadoop的方式比较流行的主要有三种 1 直接部署Apache Hadoop 即手工部署 需要自己
  • 常见的阵列技术——raid0,1,5

    常见的阵列技术 Raid0 没有容错设计的条带磁盘阵列 数据条带 并行读写 最大数据容量 成本低 速度快 一块的磁盘坏了 数据全部丢失 没有冗余 低可靠性 Raid1 相互镜像 冗余最大 快速恢复 成本高 高可靠性 最多允许一半的磁盘坏 数
  • DB扩展名的数据库文件怎么打开:两种db数据库的打开方式

    两种db数据库的打开方式 现在桌面级的各种管理系统使用的数据库都是比较常见的类型 比如Access数据库 扩展名为mdb xBase类数据库 扩展名为dbf 但有两种扩展名同为db的数据库 分属两个公司的产品 一个是老牌桌面数据库Parad
  • ssd recommended_怎么看SSD还能用多久 固态硬盘寿命检测方法【详解】

    关于固态硬盘和机械硬盘的对比 理论上来说呢 固态硬盘的寿命是不如机械硬盘的 不过实际运用情况下 SSD由于抗震能力强 实际用起来寿命可能比机械硬盘还长 毕竟很多机械硬盘都是高速旋转过程中 受到碰撞导致磁头破坏 硬盘也就坏了 下面分享几种固态
  • [CISCN2021 Quals]upload

    知识点 unicode字符替代 二次渲染绕过 目录结构识别 upload php 中限制了图片的大小 长宽 以及一些字母
  • 机器学习基础(一):平均数中位数众数

    机器学习基础 一 平均数中位数众数 从一组数字中我们可以学到什么 在机器学习 和数学 中 通常存在三中我们感兴趣的值 均值 Mean 平均值 中值 Median 中点值 又称中位数 众数 Mode 最常见的值 例如 我们已经登记了 13 辆
  • 【现代谜题】晚上有四个人要过桥,只有一个手电筒,每次过桥都需要手电筒,每次最多可同时过两个人,其中甲过桥要1分钟,乙要2分钟,丙要5分钟,丁要10分钟。求最短的过桥时间。

    文章目录 题目 一 思路 方法一 方法二 二 代码 测试数据 题目 晚上有四个人要过桥 只有一个手电筒 每次过桥都需要手电筒 每次最多可同时过两个人 其中甲过桥要1分钟 乙要2分钟 丙要5分钟 丁要10分钟 求最短的过桥时间 一 思路 首先
  • R语言基本数据结构

    R语言系列文章目录 文章目录 R语言系列文章目录 前言 一 向量 二 矩阵 三 数组 四 列表 五 数据框 前言 一篇技术博客的写作不可能面面俱到 那这就意味着必须抛弃一些内容 在R语言的书里对于R的基础知识的讲解很容易找到 因此 这一R语
  • qt文件操作

    第一步 手动获取文件路径 include
  • 动态代理简单实现

    动态代理简单实现 文章目录 动态代理简单实现 一 反射 二 反射机制提供的功能 1 相关API 2 Class类的理解 4 创建类的对象的方式 5 Class实例可以是那些结构的说明 三 类的加载过程 四 动态代理 反射的动态性 一 反射的
  • LeetCode:设计循环队列

    题目链接 622 设计循环队列 力扣 Leetcode https leetcode cn problems design circular queue 还是老套路二话不说 先上代码 typedef struct int front int
  • win11下经典jdk8的安装与环境变量的配置(一看就懂,超级详细!!!)

    1 安装jdk8 由于Oracle官网需要注册账号才能下载 不想注册的同学们可以直接通过下面的地址下载 因为新的jdk版本变化不是很大 所以初学者用1 8版本就足够啦 链接 https pan baidu com s 1HjoXGTlaPw
  • 函数栈帧的创建和销毁

    全文目录 前言 寄存器 main函数的调用 调用main函数的函数 main函数的栈帧如何开辟的 push 保存调用方的 ebp move 维护新开栈帧的栈底 sub 维护新开栈帧的栈顶 三连 push 添加栈帧的信息的变量 lea 存放栈