详解:函数栈帧的创建与销毁

news2024/10/7 3:30:18

函数栈帧的创建与销毁

  • 前期问题
  • 函数栈帧定义
  • 寄存器的种类与功能
  • 汇编指令的功能及含义
  • 图解
    • main函数之前的调用
    • 调用main函数开辟函数栈帧
    • main函数中创建临时变量并初始化
    • 为形式参数创建开辟空间
    • Add函数开辟函数栈帧,创建变量并进行运算
    • 释放Add函数栈帧
  • 前期问题解答

铁汁们~今天给大家分享一篇函数栈帧的创建与销毁,内功的修炼,来吧,开造⛳️

前期问题

  1. 局部变量是怎么创建的?
  2. 为什么局部变量的值是随机值?
  3. 函数是怎么传参的?传参的顺序是怎样的?
  4. 形参和实参是什么关系?
  5. 函数调用是怎么做的?
  6. 函数调用是结束后怎么返回的?

铁子们,以上六个问题在最后博主会做详细的解答哒~

函数栈帧定义

函数栈帧定义:在每一次函数调用,都要在栈上分配一块空间,用来保存上下文信息、函数返回值、函数参数以及一些在函数中创建的局部变量。

寄存器的种类与功能

在这里插入图片描述

铁子们,博主在这里用的是VS2019版本的编译器,建议铁铁们使用VS2013或者更低版本的编译器,不要使用更高的编译器,越高级的编译器,环境虽然稳定,但不容易进行观察和学习,不同版本的编译器在观察函数栈帧的创建与销毁是有差异的,而同一种编译器在每次进行调试观察时所对应的地址一般不同,Fn+F11逐语句进行分析,遇到函数调用时,直接进入函数中进行观察,Fn+F10为逐过程进行分析,遇到函数调用时,不进入函数中,直接进入函数调用的下一条语句,若已经进入函数调用中,摁Fn+F10则可以出函数调用。

汇编指令的功能及含义

一、调用main函数,为main函数开辟函数栈帧
第一步
在这里插入图片描述

push指令:将源操作数的地址值复制到栈顶中,在32位平台上,在将esp的地址值减去4个字节。

解释:将ebp中存放的地址值压入栈顶中,在将esp的地址值减去4个字节。
在这里插入图片描述
esp值的前后变化
在这里插入图片描述
调试时,观察esp在内存的分布,esp中存放的地址值,地址值所指向内存空间的值为ebp中所存放的地址值。
在这里插入图片描述

第二步:
在这里插入图片描述

mov指令:将源操作数的地址值(后面数据的地址值)赋值给目标操作数(前面数据),源操作数的地址值、源操作数的地址值所指向内存空间的值均不变。

解释:将esp中存放的地址值赋值给ebp,esp中存放的地址值以及其地址值所指向内存空间的值均不变。

在这里插入图片描述
第三步
在这里插入图片描述

sub指令:将在寄存器中存储的地址值减去后面数据的值,最终将结果值存放在寄存器中。

解释:将esp中存放的地址值减去地址值为0x000000e4,最终将结果值存放到esp中。
在这里插入图片描述
第四步
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

解释:依次将ebx、esi、edi压入栈中,在32位平台上,每次压入栈中esp的值每次减少4个字节。

esp值的变化:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

第五步

在这里插入图片描述

lea指令:是"load effective address"的缩写,将源操作数的地址值(后面数据的地址值)赋值给目标操作数(前面数据)。

解释:先将ebp中存放的地址值减去24h,再将ebp-24h结果值赋值给edi。
在这里插入图片描述
在这里插入图片描述
第六步:

在这里插入图片描述
解释:将9赋值给ecx,ecx中的值为执行的总次数。
在这里插入图片描述

第七步:
在这里插入图片描述
解释:将0CCCCCCCCh赋值给eax。
在这里插入图片描述

第八步:
在这里插入图片描述

rep 指令:“repeat"缩写,其后的字符串指令被重复执行。在此处表示重复执行eax的值,每次将eax中的值拷贝到edi所指向的内存空间中,每拷贝完一次,eax的值减去1,直到eax的值被减到0停止重复拷贝指令。
stos指令:“store string”,字符串储存指令。在此处表示将eax中的值拷贝到edi所指向的内存空间中。
doword:双字,四个字节。在此处表示每次被赋值的空间所内存四个字节。
ptr:"pointer"缩写,指针,其存放的地址值指向内存中一块有效的内存空间。

解释:在栈中,从地址值为edi位置处,从低地址向高地址方向的内存中依次被赋值为0CCCCCCCCh,直到ebp为止,重复ecx次,每次被赋值的内存空间为双字(四个字节)。
在这里插入图片描述
在这里插入图片描述
**第九步:**创建局部变量a,并给其赋初始值。

在这里插入图片描述
解释:将0Ah(a=10)值赋值给地址值为ebp-8所指向的内存空间。
在这里插入图片描述
第十步:创建局部变量b,并给其赋初始值。
在这里插入图片描述
解释:将14h(b=20)值赋值给地址值为ebp-14h所指向的内存空间。
在这里插入图片描述
第十一步:创建局部变量c,并给其赋初始值。

在这里插入图片描述
解释:将0(c=0)值赋值给地址值为ebp-20h所指向的内存空间。
在这里插入图片描述
第十二步:在main函数中创建形式参数(为了在调用Add函数时能够找到形式参数的值进行运算)。
在这里插入图片描述
解释:将ebp-14h值赋值给eax,并将eax压入栈顶中,在32位平台上,再将esp的值减少4个字节。将ebp-8值赋值给ecx,并将ecx压入栈顶中,在32位平台上,再将esp的值减少4个字节。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
**第十三步:**调用Add函数
在这里插入图片描述

call指令:调用函数,进入被调用的函数中,并且将程序的下一条指令压入栈中(目的:为了当调用完函数后仍能回到上一个函数中,从下一条指令依次往下执行)。

解释:调用Add函数,进入Add函数中,并且将程序的下一条指令压入栈中,在32位平台上,再将esp的值减少4个字节。

博主是摁Fn+F11进入call中,在摁Fn+F10,进入Add函数中了。

第十四步:调用Add函数,为Add函数开辟函数栈帧
在这里插入图片描述
解释:将ebp中存放的地址值压入栈顶中,在将esp的地址值减去4个字节。(为了当释放Add函数时,能够找到上一个函数的栈底,而上一个函数的栈顶很容易找到,mov esp,ebp,让其mov从而与esp一起维护上一个函数栈帧)。

在这里插入图片描述
esp地址值前后变化
在这里插入图片描述
第十五步
在这里插入图片描述
解释:将esp中存放的地址值赋值给ebp,esp中存放的地址值以及其地址值所指向内存空间的值均不变。
在这里插入图片描述

第十六步
在这里插入图片描述
解释:将esp中存放的地址值减去地址值为0CCh,最终将结果值存放到esp中。
在这里插入图片描述
第十七步
在这里插入图片描述
解释:依次将ebx、esi、edi压入栈中,在32位平台上,每次压入栈中esp的值每次减少4个字节。

esp值的变化:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第十八步
在这里插入图片描述
解释:先将ebp中存放的地址值减去0Ch,再将ebp-0Ch结果值赋值给edi。
在这里插入图片描述
在这里插入图片描述

第十九步
在这里插入图片描述
解释:将3赋值给ecx,ecx中的值为执行的总次数。
在这里插入图片描述

第二十步
在这里插入图片描述
解释:将0CCCCCCCCh赋值给eax。
在这里插入图片描述

第二十一步
在这里插入图片描述
解释:在栈中,从地址值为edi位置处,从低地址向高地址方向的内存中依次被赋值为0CCCCCCCCh,直到ebp为止,重复ecx次,每次被赋值的内存空间为双字(四个字节)。
在这里插入图片描述
第二十二步:创建局部变量z,,并给其赋初始值。
在这里插入图片描述
解释:将0(c=0)值赋值给地址值为ebp-8所指向的内存空间。
在这里插入图片描述
第二十三步:进行加法运算,计算最终的返回值。
在这里插入图片描述

add指令:加法操作指令,将两个值相加,并将最终的结果存入第一个值中。

解释:将地址为ebp+8所指向内存的值赋值给eax(拿到形参a的值)、将地址为ebp+0Ch所指向内存的值与eax中存放的值相加(拿到形参b的值、得到最终的返回值)、将eax中存放的值赋值给地址为ebp-8所指向的内存(给局部变量z,,赋结果值)。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
第二十四步:Add函数返回最终结果值。
在这里插入图片描述

注意:因为局部变量z只在其所在函数范围内有效,出了函数,就被销毁,z所占的内存空间就被还给了操作系统,所以在出函数之前就把z的值存储到寄存器中。

第二十五步:销毁为调用Add函数开辟的函数栈帧。
在这里插入图片描述

pop指令:先把目标操作数中存放的地址值所指向的内容复制给一个操作数中,在32位平台上,每次数据出栈esp的值每次增加4个字节。

解释:edi、esi、ebx依次出栈,esp的值依次增加4个字节。

esp值的前后变化:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
铁子们~因为博主不小心停止了调试,在进行调试时,在此处,esp的值相对与上一次调试esp的值已经发生了改变,但不影响最终的结果,只要在此处观察到前后esp的差值为4即可。

第二十六步
在这里插入图片描述
解释:将ebp中存放的地址值赋值给esp,ebp中存放的地址值以及其地址值所指向内存空间的值均不变。

目的:释放为调用Add函数开辟的栈帧空间,找到上一个函数main函数的栈顶
在这里插入图片描述
铁子们~因为博主不小心停止了调试,在进行调试时,在此处,esp的值相对与上一次调试esp的值已经发生了改变,但不影响最终的结果,只要在此处观察到前后esp的值与ebp的值相等即可。

第二十七步
在这里插入图片描述
解释:ebp出栈,esp的值增加4个字节。

目的:释放为调用Add函数开辟的栈帧空间,找到上一个函数main函数的栈底
在这里插入图片描述
在这里插入图片描述

铁子们~因为博主不小心停止了调试,在进行调试时,在此处,esp、ebp的值相对与上一次调试esp\ebp的值已经发生了改变,但不影响最终的结果,只要在此处观察到前后esp的值与ebp的值,前后esp的差值为4即可。

第二十八步:结束Add函数的调用,程序执行回答main函数。
在这里插入图片描述

ret指令:用于终止当前函数的执行,回到上层函数继续运行。

第二十九步:释放在main函数中为形参开辟的内存空间。
在这里插入图片描述
解释:将esp的值加上8,并将最终的结果值保存到esp中。
在这里插入图片描述
第三十步:打印运算后的结果值。
在这里插入图片描述
解释:将eax中存放的值赋值给ebp-20h(z),并将其打印到屏幕中。

第三十一步:将为调用mian函数开辟的栈帧空间还给操作系统。
在这里插入图片描述

test.c
#define _CRT_SECURE_NO_WARNINGS 1
#pragma pack(4)
#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函数之前的调用

第一步

main()函数是被__tmainCRTStartup函数调用的,而 __tmainCRTStartup又是被mainCRTStartup调用的。
在这里插入图片描述
注意:因为不同版本的编译器观察函数栈帧的创建与销毁略有差异,可能也会出现在vs2019中调试不出来main被谁调用的情况噢~

调用main函数开辟函数栈帧

第二步:为调用main函数开辟函数栈帧
在这里插入图片描述

在这里插入图片描述

main函数中创建临时变量并初始化

第三步:在main函数中创建临时变量a、b、c,并初始化。
在这里插入图片描述

为形式参数创建开辟空间

第四步:调用Add函数之前的预备工作,为形式参数创建开辟空间。
在这里插入图片描述

Add函数开辟函数栈帧,创建变量并进行运算

第五步:调用Add函数,为Add函数开辟函数栈帧,创建变量并进行运算。
在这里插入图片描述

释放Add函数栈帧

第六步:释放Add函数栈帧。
1:
执行操作1
在这里插入图片描述

在这里插入图片描述
2:执行操作2
在这里插入图片描述

在这里插入图片描述
3:执行操作3在这里插入图片描述

在这里插入图片描述
4:执行操作4在这里插入图片描述

在这里插入图片描述

前期问题解答

  1. 局部变量是怎么创建的?

先给调用main函数时开辟函数栈帧,初始化一部分空间后,在main函数栈帧中分配一些栈空间用于局部变量的创建。

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

因为在开辟mian函数栈帧时,里面已经被我们事先存放了值(这些值是随机的),如果把局部变量初始化,则把随机值进行覆盖了。

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

在调用Add函数前,我们已经把两个实参的值从右向左压入栈中,当我们真正进入Add函数中时,在Add函数栈帧里面,通过指针的偏移量找到形参。

  1. 形参和实参是什么关系?

形参是我们在压栈时候开辟的空间,为形参开辟的空间与为实参开辟的空间在内存中空间不一致(两空间是独立的),形参和实参只是值相同,所以形参的改变不会影响实参的改变。

  1. 函数调用是怎么做的?
  2. 函数调用是结束后怎么返回的?

当我们调用Add函数同时(执行call指令时),call指令也会把程序下一条执行指令的地址值压入栈中,又把上一个函数栈帧中的ebp也压入栈中了,在调用完Add函数后要返回时,pop ebp让函数可以通过压入栈中的ebp找到上一个函数栈帧的ebp,Add函数栈帧被销毁,从而回到上一个函数的栈帧空间,通过压入栈中的地址值跳转到函数调用后的下一个指令的地址处,可以让我们的函数返回,返回值通过在Add函数返回前事先存到寄存器中被带回来的。

铁铁们,分支语句和循环语句就到此结束啦,请动动你们的手给作者点个👍鼓励吧,你们的鼓励就是我的动力✨✨✨
在这里插入图片描述

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

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

相关文章

STM32F4的输出比较极性和PWM1,PWM2的关系

PWM 输出比较通道 在这里以通用定时器的通道1作为介绍。 如图&#xff0c;左边就是CNT计数器和CCR1第一路的捕获/比较寄存器&#xff0c;它俩进行比较&#xff0c;当CNT>CCR1, 或者CNTCCR1时&#xff0c;就会给输出模式控制器传送一个信号&#xff0c;然后输出模式控制器就…

基于TextCNN、LSTM与Transformer模型的疫情微博情绪分类

基于TextCNN、LSTM与Transformer模型的疫情微博情绪分类 任务概述 微博情绪分类任务旨在识别微博中蕴含的情绪&#xff0c;输入是一条微博&#xff0c;输出是该微博所蕴含的情绪类别。在本次任务中&#xff0c;我们将微博按照其蕴含的情绪分为以下六个类别之一&#xff1a;积…

Docker部署nacos2.1版本集群

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称&#xff0c;一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。 Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集&#xff0c;帮助您快速实现动态服…

spring发送qq邮件 + 模板引擎

文章目录 学习链接邮箱配置开启qq邮箱服务相关配置文件 freemarker模板引擎引入依赖配置freemarker编写模板registerTpl.ftl 发送带内嵌图片的邮件 附件效果 学习链接 java邮件发送 Java实现邮件发送 springboot发送QQ邮件&#xff08;最简单方式&#xff09; 刘java-Java使用…

css - 盒子水平垂直居中的几种方式

前端盒子水平垂直居中的几种方式 实现效果图如下&#xff1a; 首先是父元素的基本样式&#xff1a; .container {width: 600px;height: 600px;border: 1px solid red;background-color: antiquewhite;margin: 0 auto;/* 父盒子开启相对定位 */position: relative;}1&#xf…

【Linux】Linux入门学习之常用命令三

介绍 这里是小编成长之路的历程&#xff0c;也是小编的学习之路。希望和各位大佬们一起成长&#xff01; 以下为小编最喜欢的两句话&#xff1a; 要有最朴素的生活和最遥远的梦想&#xff0c;即使明天天寒地冻&#xff0c;山高水远&#xff0c;路远马亡。 一个人为什么要努力&a…

Python每日一练(20230511) 跳跃游戏 I\II\III\IV

目录 1. 跳跃游戏 Jump Game I 2. 跳跃游戏 Jump Game II 3. 跳跃游戏 Jump Game III 4. 跳跃游戏 Jump Game IV &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日一练 专栏 1. 跳跃游戏 Jump Game …

操作符知识点大全(简洁,全面,含使用场景,演示,代码)

目录 一.算术操作符 1.要点&#xff1a; 二.负数原码&#xff0c;反码&#xff0c;补码的互推 1.按位取反操作符&#xff1a;~&#xff08;二进制位&#xff09; 2.原反补互推演示 三.进制位的表示 1.不同进制位的特征&#xff1a; 2.二进制位表示 3.整型的二进制表…

如何利用python实现灰色关联分析?

1.灰色关联分析简介 灰色系统这个概念是相对于白色系统和黑色系统而言的。从控制论的知识里&#xff0c;颜色一般代表对于一个系统我们已知信息的多少&#xff0c;白色代表信息量充足&#xff0c;黑色代表我们其中的构造并不清楚的系统&#xff0c;而灰色介于两者之间&#xf…

WhatsApp如何让客户参与变得更简单?

WhatsApp对你的品牌来说可能和Twitter和Facebook一样重要&#xff0c;你可能已经把它们纳入你的社交媒体战略。 是的&#xff0c;WhatsApp不仅仅可以用来给同事发短信或与远方的亲戚视频聊天&#xff0c;它也适用于商业。 在发展WhatsApp业务时&#xff0c;小企业主得到了最优…

K8s基础9——服务发现Coredns、Ingress Controller多种暴露方式、TLS+DaemonSet、Headless Services

文章目录 一、服务发现机制1.1 环境变量注入1.2 DNS解析 二、Ingress4.1 部署Ingress controller4.2 暴露Ingress Controller4.2.1 SVC NodePort方式4.2.2 共享宿主机网络方式 4.3 默认后端4.4 同域名不同URL转不同服务4.5 不同域名转不同服务4.6 使用https4.6.1 安装cfssl4.6.…

如何用 Serverless 一键部署 Stable Diffusion?

作者 | 寒斜&#xff08;阿里云智能技术专家&#xff09; 思路 其实很简单&#xff0c; 我们只需要将镜像里面的动态路径映射到 NAS文件存储里面即可&#xff0c;利用 NAS 独立存储文件模型&#xff0c;扩展&#xff0c;语言包等&#xff0c;并且我们可以为管理 NAS 单独配置…

使用Python和Django构建一个全功能的在线医疗问诊平台

在线医疗问诊平台应运而生&#xff0c;为患者和医生之间提供了一个便捷的交流平台。本文将介绍如何使用Python和Django构建一个全功能的在线医疗问诊平台。 功能 在我们的平台上&#xff0c;患者可以注册账户、查询医生、预约诊断、支付费用并与医生沟通。医生可以创建个人档…

【.NET CORE】使用Rotativa.AspNetCore将网页转换为PDF

插件功能&#xff1a;将在线网页转换为PDF显示&#xff0c;文件保存 组件配置&#xff1a; 1、在NuGet管理中搜索Rotativa.AspNetCore并安装稳定版&#xff0c;项目github地址&#xff1a;GitHub - webgio/Rotativa.AspNetCore: Rotativa for Asp.Net Core 2、github下载项目…

Docker安装部署MySQL

1、拉取镜像 docker pull mysql:8.0 2、查看镜像 docker images 3、创建文件夹 mkdir ~/mysql cd mysql/ 4、创建并启动MySQL容器 docker run -id \ > -p 3306:3306 \ > --namec_mysql \ > -v $PWD/conf:/etc/mysql/conf.d \ > -v $PWD/logs:/logs \ > -…

Vue Emelent-UI表格合并行或列rowspan和colspan的作用

Vue Element-UI的table组件支持合并行或者列&#xff0c;在这里做个简单的学习笔记。 我们可以通过rowspan和colspan来进行单元格合并&#xff0c;那么这两个属性是什么意思呢&#xff0c;通过官方给的demo来探讨下。 上述单元格将行index为奇数的第一列和第二列合并为一个单…

python dict 取值方法

在日常工作中&#xff0c;我们经常会遇到需要将一些数据转换为 dict格式的情况。比如&#xff1a; 1、想要将多个数组按照某种规则进行排列&#xff0c;形成有序的数据表&#xff0c;这时需要使用 dict函数。 3、想要将数据按照指定的方式进行存储&#xff0c;比如&#xff1a;…

Maven自定义配置

修改maven默认字符编码 maven默认编码为GBK 注:配好MAVEN_HOME的环境变量后,在运行cmd. 打开cmd 运行mvn -v命令即可. 修改UTF-8为默认编码.设置系统环境变量 变量名MAVEN_OPTS 变量值-Dfile.encodingUTF-8 还可以添加其他配置&#xff0c;比如&#xff1a; -Xms256m -Xmx512m…

IDEA编译JDK1.8源码及运行测试

———————————————— 版权声明&#xff1a;本文为CSDN博主「神韵499」的原创文章&#xff0c;遵循CC 4.0 BY-SA版权协议&#xff0c;转载请附上原文出处链接及本声明。 原文链接&#xff1a;https://blog.csdn.net/qq_41055045/article/details/112002440 ————…

【Qt编程之Widgets模块】-004:QTableWidget及基本操作

QTableWidget及基本操作 1. 概述2. 主要操作函数2.1 QTableWidgets实例化2.2 设置表头 setHorizontalHeaderLabels2.3 单元格选择&#xff1a;setSelectionBehavior2.4 设置列数 setColumnCount2.5 设置行数 setRowCount2.6 网格的显示 setShowGrid2.7 添加表项 setItem2.8 表项…