函数栈帧的创建与销毁(保姆级讲解)

局部变量是怎么创建的?

在为main函数开辟栈帧空间时,在一定范围内初始化成0CCCCC,再把里面0CCCC的一些开辟空间给局部变量使用。

为什么局部变量的值是随机值?

因为我们在为main函数开辟栈帧空间时,会将一定范围内空间初始成0CCCCCC里面什么也没有,所以如果局部变量不给初始化,局部就会进入随意开辟栈帧空间,就是为随机值或者是烫烫烫。

函数是怎么传参的?传参的顺序是怎样的?

eax(b)和ecx(a)进行压栈,先传的b再传的a,从右向左。

形参和实参是什么关系?

形参是实参的一份临时拷贝,它们的值是相同的,但所使用的空间是不同的,所以形参的改变不影响实参,形参确实只是实参的一份临时拷贝。

函数调用是怎么做的?

下面我画图所解释的非常清楚了。(如果不明白的同学可以私信互相交流下)

函数调用是结束后怎么返回的?

由函数一步一步建立空间,再一步一步销毁空间返回

用保存call指令下一条指令地址与ebp-main函数的保存位置进行寄存器返回值

用寄存器eax返回最终的值。


知道和函数栈帧的创建和销毁就都会了,其实就是修炼了自己的内功,也能搞懂后期更多的知识。
进入正题
今天讲解使用的环境是VS2019
同时在不同的编译器下,函数调用过程中栈帧的创建是略有差异的,具体细节取决于编译器的实现。

首先我们要了解什么是函数栈帧

函数栈帧就是在函数调用过程中,程序为函数所开辟的栈空间,函数一般放在栈区。

而编译器为了方便动态内存管理,一般划分为了三个区域:栈区 堆区 静态区

而什么又是栈呢?

栈的概念及结构
栈:一种特殊的线性表,其只允许在 固定的一端 进行 插入和删除 元素操作。 进行数据插入和删除 操作的一端称为 栈顶 ,另一端称为 栈底 。栈中的数据元素遵守 后进先出 LIFO (Last in First Out) 的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈(Push), 入数据在栈顶
出栈: 栈的删除操作叫做出栈(Pop)。 出数据也在栈顶 。
特点:栈只能在栈顶进行插入和删除。

为了更加清楚了解函数栈帧,我们还需要了解下以下寄存器

eax :保留临时数据,常用于返回值

ebx:保留临时数据

ecx

edx

ebp:栈低指针

esp:栈顶指针

ebp和esp这两个寄存器存放的是地址,这两个地址用来维护函数栈帧的。

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

接下来以如下图代码来解释函数栈帧的创建与销毁过程

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

在调用main函数时ebp和esp会维护main函数的函数栈帧。

当我按F10进入调试,并且打开调用堆栈


我们可以看见函数的调用关系,调用堆栈是反应函数的调用关系的

由调试我们可以看见main函数是被invoke_main()函数调用的

而Add函数是被main()函数调用的

那么理所当然的invoke_main函数与Add函数也是有自己的函数栈帧空间的,并且由ebp和esp来维护函数栈帧空间。

F10进入调试点击右键转到反汇编

如下图所示是此次代码的反汇编指令 

但我们为了方便更加清晰的观察,再次点右键取消显示符号名。

 

main函数第一条反汇编指令是Push(压栈) ebp。

如果是ebp压栈那么ebp的值是会变小的 因为是由高地址到低地址

这是原来的值

当我push以后的值,果然变小了

 所以是真的压进去了吗?我们可以通过内存来查看

当我们查看esp内存的地址时,ebp确实压进去了,因为esp内存的值是ebp的地址

fc f9 f3 00  VS编译器一般是:小端字节存储 低位字节数据放低地址处 高位字节数据放高地址处 

mov的意思是把esp的值给ebp 我们依然可以通常调试来查看是不是这样的

sub是subtraction减法的缩写。就是给esp减去0E4h 

 用16进制显示就是228

 当我们给esp减去所对应的值时,那么esp不能再指向原来的位置,而是指向了上一块的某块区域

当我们查看内存ebp和esp

ebp

esp

这些内存空间都是为main函数所开辟的空间 

接下来是在栈顶压3个元素 

随着压栈esp也会随着压栈指向位置会发生变化 

 

VS2019栈区内存存放习惯:先放高地址,再放低地址

下面esp的值会随着压栈 esp位置也产生了变化

lea指令是=load effective address. 意思是加载有效地址

 这个指令有效果的其实是rep stos意思是把edi开始下面将内存空间改成OCCCCCCCCh 以双倍字节开辟 dword=double word  es:[edi]把edi开始下面所有空间以双字节开辟成0CCCCCCCCh。

edi到ebp开辟内存空间

esp-24h的值如下图

 

 将十进制数0Ah放进ebp-8 就是相当于把10放到了ebp-8里面

如果不给a初始值那么就是随机值,因此在为main函数开辟空间时使用的就是CCCCCC的值,所以会出现烫烫烫(字符串 字符)或者随机值(变量)。

 

0a 00 00 00  就是10的十六进制存储 内存存储一般是十六进制存储

又是隔着两个字节存放的C的值0 (不同编译器存放位置不同,取决于编译器)

 

把20的值给eax再把eax压栈压进去

下一步指令把10给ecx然后再把ecx进行压栈

 按F11进入call令

内存中存放的是下一条add指令的地址00171987

 当我们再按一次F11会跳到Add函数的反汇编指令当中去

这和main函数的反汇编极其相似,这是在为Add函数开辟函数栈帧空间

 第一步Push压栈 第二步mov esp给ebp 第三步把0cch 给esp 相当于esp又往上走了

 

 

 

再进行压栈 

随着压栈esp的值也变化 esp的指向位置也随着变化

把ebp-0ch值给edi 把3给ecx 然后从edi开始下面所有位置改成0CCCCCCCH

 

 

 把0放到ebp-8 

 

 

 

 

ebp+0ch相当于+12 epb+12

通过调试过程看见传参是从右向左,先传的b再传的a

最后返回的时候把ebp-8的值也就是z的值给了寄存器eax

下面指令pop三次 esp也随着产生位置变化

当pop了三次esp的值 增加了3次

当pop三次要返回main函数 那么Add创建的函数栈帧就要销毁 这几个指令完成了把esp的值给ebp

再pop ebp 我们这个位置所保存main函数的ebp-main 就是为了函数返回时找到main函数的ebp

返回以后ebp和esp又开始维护了main函数的函数栈帧空间

这条指令就是为了执行call指令下一条add的功能 弹出了add指针地址

所以我们在开辟函数栈帧时保存了add指令地址

 当我们再按F10就回到了main函数Add的位置

main函数add指令 00171987与我们刚才在函数栈帧保存的call指令下一条指令的地址一模一样,也就是add指令函数的地址。就是为了方便回来,简直就是荣归故里!设计的太牛了!

给esp+8

随着esp+8形参的空间也销毁了。

把eax 30的值给ebp-20h 就是刚才c的位置

然后在程序结束时寄存器会返回所对应的值。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【云原生】都在说云原生?到底什么是云原生?

文章目录 一、云原生是什么云原生云原生 二、云原生四要素微服务容器化DevOps 三、具体的云原生技术有哪些容器(Containers)微服务(Microservices)服务网格(Service Meshes)不可变基础设施(Immutable Infrastructure)声明式API(Deciarative API) 四、云服务器相对传统物理服务器…

小程序的入门

目录 小程序的简介 好处 安装及使用 小程序的入门案列 小程序的简介 微信小程序是一种轻量级的应用程序&#xff0c;可以在微信平台上运行。它们具有快速、便捷和低成本等特点。通过微信小程序&#xff0c;用户可以在微信内直接使用各种功能&#xff0c;而无需下载和安装额外…

python pip安装超时使用国内镜像

网络环境差的时候需要我们独立的进行相对应的包下载离线安装&#xff0c;或者给pip 加上 国内的镜像源比如加上清华的镜像源&#xff1a; 参考网址&#xff1a;pypi | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror https://mirrors.tuna.tsinghua…

SpringCloud-Hystrix

一、介绍 &#xff08;1&#xff09;避免单个服务出现故障导致整个应用崩溃。 &#xff08;2&#xff09;服务降级&#xff1a;服务超时、服务异常、服务宕机时&#xff0c;执行定义好的方法。&#xff08;做别的&#xff09; &#xff08;3&#xff09;服务熔断&#xff1a;达…

浅析倾斜摄影三维模型(3D)几何坐标精度偏差的几个因素

浅析倾斜摄影三维模型&#xff08;3D&#xff09;几何坐标精度偏差的几个因素 倾斜摄影是一种通过倾斜角度较大的相机拍摄建筑物、地形等场景&#xff0c;从而生成高精度的三维模型的技术。然而&#xff0c;在进行倾斜摄影操作时&#xff0c;由于多种因素的影响&#xff0c;导致…

【算法学习】-【滑动窗口】-【长度最小的子数组】

LeetCode原题链接&#xff1a;209. 长度最小的子数组 下面是题目描述&#xff1a; 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的连续子数组 [numsl, numsl1, …, numsr-1, numsr] &#xff0c;并返回其长度。如…

PCL点云处理之基于强度特征的SIFT关键点提取法 (二百一十五)

PCL点云处理之基于强度特征的SIFT关键点提取法 (二百一十五) 一、算法介绍二、具体实现1.代码2.效果一、算法介绍 继续SIFT关键点的提取介绍,之前已经基于高程和颜色分别提取了关键点,这里是基于强度信息,若遇到文件无法读取强度问题,请参考上一篇博文,下面是具体的实现…

互联网Java工程师面试题·Java 并发编程篇·第四弹

目录 39、volatile 有什么用&#xff1f;能否用一句话说明下 volatile 的应用场景&#xff1f; 40、为什么代码会重排序&#xff1f; 41、在 java 中 wait 和 sleep 方法的不同&#xff1f; 42、用 Java 实现阻塞队列 43、一个线程运行时发生异常会怎样&#xff1f; 44、…

数据结构--》掌握数据结构中的查找算法

当你需要从大量数据中查找某个元素时&#xff0c;查找算法就变得非常重要。 无论你是初学者还是进阶者&#xff0c;本文将为你提供简单易懂、实用可行的知识点&#xff0c;帮助你更好地掌握查找在数据结构和算法中的重要性&#xff0c;进而提升算法解题的能力。接下来让我们开启…

修炼k8s+flink+hdfs+dlink(四:k8s(一)概念)

一&#xff1a;概念 1. 概述 1.1 kubernetes对象. k8s对象包含俩个嵌套对象字段。 spec&#xff08;规约&#xff09;&#xff1a;期望状态 status&#xff08;状态&#xff09;&#xff1a;当前状态 当创建对象的时候&#xff0c;会按照spec的状态进行创建&#xff0c;如果…

Hadoop3教程(四):HDFS的读写流程及节点距离计算

文章目录 &#xff08;55&#xff09;HDFS 写数据流程&#xff08;56&#xff09; 节点距离计算&#xff08;57&#xff09;机架感知&#xff08;副本存储节点选择&#xff09;&#xff08;58&#xff09;HDFS 读数据流程参考文献 &#xff08;55&#xff09;HDFS 写数据流程 …

【Vue 2】Props

Prop大小写 Prop的命名规则有camelCase&#xff0c;驼峰命名和kebab-case&#xff0c;短横线分隔。 由于HTML对大小写不敏感&#xff0c;所以浏览器会把大写字母解释为小写字母。 当我们使用camelCase命名prop时&#xff0c;在Dom中的template模板使用该prop就需要换成对应的…

Leetcode刷题详解——盛最多水的容器

1.题目链接&#xff1a;盛最多水的容器 2.题目描述&#xff1a; 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容…

修改http_charfinder.py使能在python311环境中运行

需要修改两个函数&#xff0c;第一个是init函数&#xff0c;修改如下&#xff1a; async def init(loop, address, port): # <1> # app web.Application(looploop) # <2> # app.router.add_route(GET, /, home) # <3> app web.Application(…

企业级CI/CD 持续集成/交付/发布

jenkins 安装与使用 nmcli g hostname jenkins 加载缓存 yum makecache fast 上传jdk11、jdk8 获取、上传war包 1、jenkins.io/download 2.4.27 2、老师发的 上传 maven 上传tomcat软件包 &#xff08;apache.org-tomcat8-下载&#xff09; 注意8009端口 /usr... vi /etc/pro…

Redis 图形化界面下载及使用超详细教程(带安装包)! redis windows下客户端下载

另一个完全不同的redis图形化界面教程链接&#xff08;带安装包&#xff09;&#xff1a; Redis 最流行的图形化界面下载及使用超详细教程&#xff08;带安装包&#xff09;&#xff01; redis windows客户端下载_dream_ready的博客-CSDN博客 redis图形化界面的压缩包&#xff…

18.(开发工具篇Gitlab)Git如何回退到指定版本

首先: 使用git log命令查看提交历史,找到想要回退的版本的commit id. 使用git reset命令 第一步:git reset --hard 命令是强制回到某一个版本。执行后本地工程回退到该版本。 第二步:利用git push -f命令强制推到远程 如下所示: 优点:干净利落,回滚后完全回到最初状态…

Leetcode刷题笔记--Hot61-70

1--课程表&#xff08;207&#xff09; 主要思路&#xff1a; 用 in 记录每一门课程剩余的先修课程个数&#xff0c;当剩余先修课程个数为0时&#xff0c;将该课程加入到队列q中。 每修队列q中的课程&#xff0c;以该课程作为先修课程的所有课程&#xff0c;其剩余先修课程个数…

K8S云计算系列-(3)

K8S Kubeadm案例实战 Kubeadm 是一个K8S部署工具&#xff0c;它提供了kubeadm init 以及 kubeadm join 这两个命令来快速创建kubernetes集群。 Kubeadm 通过执行必要的操作来启动和运行一个最小可用的集群。它故意被设计为只关心启动集群&#xff0c;而不是之前的节点准备工作…