从汇编代码探究函数栈帧的创建和销毁的底层原理

news2025/2/13 12:56:06

人,只有在放弃战斗的时候才算输,只要坚持战斗,就还没输 

本文收录于青花雾气-计算机基础

往期回顾

从0到1搞定在线OJ

数据在内存中的存储

计算机存储的大小端模式

目录

一、先导知识

二、函数调用堆栈

三、函数栈帧的创建

1.创建函数栈帧

2.创建变量

3.函数传参

4.函数调用

四、函数栈帧的销毁


大家好,我是纪宁。

这篇博客将从底层原理加汇编代码剖析函数栈帧的创建和销毁的过程

注:本文使用的编译器:Visual Studio 2013(不用更高版本VS的原因:新的编译器为了考虑更多的问题,分装的更加复杂,不容易抽离出函数栈帧的创建过程

编译器不同,存储位置可能会有差异,但思想是一样的

学了这篇文章,你会想清楚曾经一直困扰你很久的一些问题,如

  • 局部变量是怎么创建的
  • 为什么局部变量的值是随机值
  • 函数值怎么传参的?传参的顺序是怎样的
  • 形参和实参是什么关系
  • 函数调用到底是怎么做的
  • 函数调用结束后是如何返回的

一、先导知识

C/C++中内存分为3个区域:栈区、堆区、静态区

不同性质的变量存放在不同的内存区域中,下图是各种变量所在内存中的区域

本文所讲的函数栈帧的创建和销毁过程就是在栈区进行的

栈区存放变量的特点:先存高地址,再存低地址。

销毁变量的时候是先销毁低地址里面的变量,再销毁高地址里面的变量,如图

例如:变量1先创建,再创建变量2......创建变量8;但是销毁的时候,就是先销毁变量8......最后销毁变量1

 本文中将用到的两个寄存器:ebp、esp,这两个指针中存放的是地址,用来维护函数栈帧

ebp是栈底指针、esp是栈顶指针正在调用哪个函数,ebp、esp就维护哪个函数的函数栈帧

二、函数调用堆栈

在vs2013中来测试并观察main函数栈帧的创建过程

F10-->调试-->窗口-->调用堆栈  一直按F10直到程序结束,就会出现下面的场景

 可以看到main函数是由626行的那个函数  __tmainCRTStartup函数调用的

 而  __tmainCRTStartup 函数又是由466行的   mainCRTStartup 函数调用的,调用的顺序正是由栈顶往栈底

三、函数栈帧的创建

1.创建函数栈帧

有(二)中可知,VS2013中,main函数也是被其他函数调用的,每一次函数调用都要在栈区上分配空间,接下来就通过观察汇编代码来看观看函数栈帧的创建,假设mian函数中还有一个Add函数,来计算两个数的相加的和。

按下F10后不要乱动,右击鼠标,转到反汇编(C语言对应的汇编代码)

这就是这段代码对应的汇编代码 

由于main函数是由 __tmainCRTStartup函数调用的,所以在调用main函数前,main函数的函数栈帧已经创建好了

 push:给栈顶放一个元素

push          ebp 将ebp里面的值放在栈顶,同时因为esp指向的是栈顶,所以esp指针也‘上移’

如图

mov:将前面的值放到后面值的地方

mov        ebp,esp,将ebp指针移动到esp指针的位置

sub:前面的值减去后面的值

sub         esp,0E4h,指针esp减去0E4h(一个8进制的数字),并移动指针esp

由于ebp是栈底指针,esp是栈顶指针,正在调用哪个函数,ebp和esp就维护哪个函数的函数栈帧

后面调用Add函数的时候,ebp、esp就去维护Add函数的函数栈帧。

所以栈区中mian函数的调用空间(预开辟)就开辟好了

连续push三次,将这三个元素依次放到栈顶,压栈三次

同时esp也移动到栈顶

lea:load effecitive address   加载有效地址 

lea             edi,[ebp-0E4h]  把  ebp-0E4h 的地址放到edi里面去,而ebp-0E4h应该就是main函数的栈顶地址

mov           ecx,39h 把39h的值放入eax中去

mov           eax,0CCCCCCCCh 把0CCCCCCCCh的值放入eax中

rep stos     dword ptr es:[edi] 把从 edi(edp-0E4h开始向下ecx(39h)次 dword 的数据全部都改成 eax0CCCCCCCC) (dword(双字节))图例

简单的来讲就要将从 edp-0E4h 到 ebp 中间所有的内容初始化为0ECCCCCCCC

假设初始化完了(没有画全),意思就是中间全部初始化为0CCCCCCCC

此时main函数才开始执行代码

2.创建变量

在代码的反汇编部分,右击鼠标,将显示符号名关掉

一直按F10到Add之前的汇编代码处

mov         dword ptr [ebp-8],5       将5存入  ebp-8 的位置

mov         dword ptr [ebp-14h],2   将2存入  ebp-14h(4+1*16=20(ebp-20))的位置

mov         dword ptr [ebp-20h],0   将0存入  ebp-20h(0+2*16=32(ebp-32))的位置

存储之后的内存图

这样就创建了变量a,b,c

3.函数传参

mov         eax,dword ptr [ebp-14h]   将ebp-14h(其实就是b的值)的值放入eax(寄存器)里面去

push        eax  eax压栈,将eax放在栈顶

mov         ecx,dword ptr [ebp-8]   将ebp-8(其实就是a的值)的值放入ecx(寄存器)里面去

push        ecx  ecx压栈 ,将ecx放栈顶

传参---先传b,再传a (说明函数传参是从右向左的)

4.函数调用

call--函数调用

执行到此处要按F11进入函数内部 

观察内存后,发现执行完call后,call指令的下一条指令的地址被压栈

当后面执行完Add函数后,通过这个地址可以找到调用位置并继续向下执行代码

再按F11就进入了Add函数,发现前面的指令与main函数里面一样,说明函数栈帧的创建过程是相同的

与刚开始创建mian函数栈帧的过程相同,唯一不同的就是不同函数栈帧开辟的空间大小有差异,这个空间大小取决于编译器

同样,由于正在调用Add函数,ebp和esp就来维护Add函数的函数栈帧

同样,在Add函数中开始执行代码

mov         eax,dword ptr [ebp+8]  ebp+8的值赋给eax

add         eax,dword ptr [ebp+0Ch]   将ebp+0Ch的值与eax的值相加并赋值给eax

mov         dword ptr [ebp-8],eax  将eax的值赋给ebp-8

eax是一个寄存器,可以存储变量/变量的值

说明真进行函数调用的时候,形参并不是在Add函数内部创建的,而是在函数调用刚开始的时候,通过压栈,将参数a,参数b传上去了,且参数b是先传的

在使用形参的时候,回头找到了以前压栈的这块空间,说明形参确实是实参的一份临时拷贝

形参并没有真再创建自己的空间

最后将形参计算的结果存入ebp-8中

但是如果在函数里面创建变量的话,它的内存是单独分配的

四、函数栈帧的销毁

 先将计算结果ebp-8(z)里面的值存在寄存器中,方便后面将Add的函数栈帧销毁后返回

三次pop,从栈顶删去三个元素

将ebp赋为esp(说明esp移动到ebp位置,此时栈顶发生变化,到了ebp的位置)

然后从栈顶删去一个元素放在ebp里面,esp继续向下。

因为此时栈顶放的是main函数的栈底地址,所以,ebp通过这个地址找到了main函数的栈底

此时就从Add函数里面顺利的回到了main函数里面

因为00F83420是调用函数的指令call的下一条指令的地址,所以恰好可以让代码继续运行

这就是上面为什么要将call下一条指令的地址存起来

确保‘走出去’,还能‘找回来’ 继续运行add这一条指令

然后ret(return):将栈顶的那个指针pop(出栈) 

esp(栈顶指针)再向下移动

此时esp+8,即将栈顶 + 8 就相当于将形参x,y销毁,如图

然后mov 将寄存器eax中存的Add函数的计算结果放在ebp-20h中,即c中

此时要进入printf函数,虽然printf已经封装好了,但printf函数的调用还是要经历函数栈帧的创建和销毁这个过程,方法与Add函数相同,这里就不过多赘述

那么return 0就是main函数栈帧的销毁过程

 最后的结果就是main函数的栈帧也被销毁

在这里插入图片描述

学习新知识的过程大多是枯燥和乏味的,放弃很容易,但坚持一定很酷

如果你也能和我一样坚持学习,那我只能说

泰裤辣!

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

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

相关文章

计算机图像处理—HOG 特征提取算法

一、实验介绍 1. 实验内容 本实验将学习HOG 特征提取算法。 2. 实验要点 HOG 算法HOG 算法有效的原因创建 HOG 描述符HOG 描述符中的元素数量可视化 HOG 描述符理解直方图 3. 实验环境 Python 3.6.6numpymatplotlibcv2copy 二、实验步骤 简介 正如在 ORB 算法中看到的…

机器学习—支持向量机

练习5:支持向量机 介绍 在本练习中,我们将使用支持向量机(SVM)来构建垃圾邮件分类器。 在开始练习前,需要下载如下的文件进行数据上传: data.tgz -包含本练习中所需要用的数据文件 其中: e…

华为OD机试 JavaScript 实现【计算字符串的编辑距离】【牛客练习题 HJ52】,附详细解题思路

一、题目描述 Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。编辑距离的算法是首先由…

后端(二):Servlet

我们上一张聊的是Tomcat,它其实就是一个 HTTP 服务器,而Servlet 是基于 Tomcat 的 原生api ,除了 Servlet,后面还有聊到很多 api 。 Servlet 是什么 Servlet(Server Applet)是Java Servlet的简称&#xf…

【知识点复习】结构体与共用体

结构体和共用体各有什么特点: 1、结构体中每一个成员都有自己的内存空间,计算结构体大小的时候要注意内部字节对齐; 32位占4字节,64位占8字节。 结构体访问成员:点降级访问 2、共用体又叫联合体union,每一…

我的256创作纪念日

机缘 挺开心的,想到自己未曾写过一些非技术类的博客,恰巧今天刚好也是我的256创作纪念日,就乘着这个日子,写一点自己过去的收获、内心的想法和对未来的展望吧。 本人不才,只就读于一所民办本科之中,我挺不想…

ASCON:以“慢而稳”赢得NIST轻量级加密算法标准

1. 引言 自2016年以来,NIST一直在评估轻量级加密方法,并于2022年发布了入围决赛的10种轻量级加密算法: ASCONElephantGIFT-COFBGrain128 AEADISAPPhoton BeetleRomulusSparkleTinyJambuXoodyak 在评估过程中,NIST重点关注&#…

使用Python把文件夹里面的图片放入一个pdf

文章目录 背景介绍代码代码分析展示 背景介绍 在看一位up主的“矩阵分析”课程的时候,up主的课件是以图片形式保存在QQ空间的。图片形式不便于学习,所以想要通过Python代码,把保存在“矩阵分析课件”里面的图片,转换为pdf&#x…

Qt函数运用

setwidget 文件 文件读写 链接 std::ifstream---std::ofstream 头文件--#include <fstream> 执行都是类&#xff0c;用这些类操作文件都要建立对象流。 1&#xff0c;建立对象流 流对象的建立有两种方式&#xff1a; &#xff08;1&#xff09;使用fstream类可以…

内网安全:Socks 代理 || 本地代理 技术.

内网安全&#xff1a;Socks 代理 || 本地代理 技术. Socks 代理又称全能代理&#xff0c;就像有很多跳线的转接板&#xff0c;它只是简单地将一端的系统连接到另外一端。支持多种协议&#xff0c;包括http、ftp请求及其它类型的请求。它分socks 4 和socks 5两种类型&#xff0…

Selenium中的隐式等待和显式等待

在Selenium中&#xff0c;“等待”在执行测试中起着重要作用。在本文中&#xff0c;您将学习Selenium中“隐式”和“显式”等待的各个方面。 在本文中&#xff0c;您将学习到 1. 为什么我们需要在selenium中等待&#xff1f; 2. 隐瞒等待 3. 明确等待 4. 流利的等待 为什么…

15.DIY可视化-拖拽设计1天搞定主流小程序-分类联动文章列表实时刷新

分类联动文章列表实时刷新 本教程均在第一节中项目启动下操作 分类联动文章列表实时刷新前言需求一:功能实现:点击首页分类,对应分类内容显示到当前页一、清空原分类界面:二. 设置选项卡三:设定展示内容字段:1.跨页面复制:文章分类组件到分类![在这里插入图片描述](https://img…

服务器安装cuda版本的pytorch+DGL

1、先创建pytorch环境&#xff1a;conda create -n ljj_torch112 python3.8 看本机的&#xff1a; 先看自己的cuda版本&#xff1a;&#xff08;最权威的看&#xff1a;nvcc --version&#xff09; 10.0的cuda于是不太符合&#xff0c;所以换一个10.2的cuda比较常用&#xff0…

【C++从入门到放弃】list深度剖析及模拟实现

&#x1f9d1;‍&#x1f4bb;作者&#xff1a; 情话0.0 &#x1f4dd;专栏&#xff1a;《C从入门到放弃》 &#x1f466;个人简介&#xff1a;一名双非编程菜鸟&#xff0c;在这里分享自己的编程学习笔记&#xff0c;欢迎大家的指正与点赞&#xff0c;谢谢&#xff01; list …

CANN黑科技解密|昇腾Ascend C编程语言 — 极简易用的算子开发体验

AI应用的大脑是神经网络&#xff0c;而构成神经网络的基石是一个个算子。为了让开发者的网络在昇腾硬件上高效运行&#xff0c;昇腾异构计算架构CANN&#xff08;Compute Architecture for Neural Networks&#xff09;提供了丰富的高性能算子库&#xff0c;包括神经网络库、线…

Python-opcua 编程(1)

任何一项新标准如果不能充分应用是不可能推广的&#xff0c;最近看了一些国外网站&#xff0c;发现类似OPC UA 的应用以及比较广泛了&#xff0c;而且有许多课程。相比之下&#xff0c;我国OPCUA 标准的普及工作仍然停留在概述的阶段&#xff0c;为此&#xff0c;我将逐步介绍一…

复习并发编程的基础知识(一)

时间长了&#xff0c;并发编程的基础知识总忘&#xff0c;来记录一下&#xff1a; 进程和线程 进程&#xff1a;资源分配的最小单元&#xff0c;什么是资源&#xff1f;CPU&#xff0c;内存&#xff0c;网卡等等 线程&#xff1a;进程中的一个实体&#xff0c;不能单独存在&…

七、DMSP/OLS、NPP/VIIRS等夜间灯光数据能源碳排放空间化——能源碳排放增长类型、增长率、总量增长等级分析

一、前言 前文对能源碳排放空间化后的分析角度做了一些介绍,其实无非就是能源碳排放增长类型、增长率等的计算,那么这里强调一下,这个时候不能用利用统计数据计算出来的能源碳排放数据进行计算,而是必须用反演的能源碳排放数据进行划定计算。 二、具体步骤 增长类型 (…

【Excel超实用快捷键!!!办公效率1000%up!up!up!】

目录索引 ctrle&#xff1a;提取数据&#xff1a;合并数据&#xff1a; 普通快捷键&#xff1a;ctrla&#xff1a;ctrlc&#xff1a;ctrlv&#xff1a;ctrlx&#xff1a;ctrlz&#xff1a;ctrly&#xff1a;ctrls&#xff1a;ctrlf&#xff1a; 文字格式快捷键&#xff1a;ctrl…

IMX6ULL裸机篇之SPI实验-SPI主控寄存器

一. SPI主控寄存器 IMX6ULL 芯片的 SPI接口叫做 ECSPI&#xff0c;支持全双工、主丛可配置。 本文学习 IMX6ULL-阿尔法开发板SPI中控芯片的 SPI寄存器部分。后续代码实现需要配置 SPI相关的寄存器。 二. SPI主控的寄存器配置 1. SPI主控芯片寄存器 (1) RXDATA寄存器&am…