函数栈帧的创建和销毁

news2024/12/29 9:16:38

“总有人间一两风,

填我十万八千梦”

🍑作者:小赛毛

💕文章初次日期:2022/11/21


目录

函数栈帧解决了什么问题?

什么是栈?

什么是寄存器?

  函数栈帧的创建和销毁

预热知识准备:

 函数的调用堆栈

转到反汇编

函数栈帧的创建

补充说明:烫烫烫~

调用Add函数:

函数栈帧的销毁


大家学了这么久C语言有没有产生一些问题与疑惑哩?

好家伙,你问俺啥问题,那我只能说6哈哈哈哈

比如噻

函数栈帧解决了什么问题?

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

老铁这会儿可能又要说,你小子,哪来这么多问题?

 其实哩,老铁们呢只要知道函数栈帧的创建和销毁,学会了就明白这些问题了,内功呢自然也就修炼了~

我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。
那函数是如何调用的?函数的返回值又是如何待会的?函数参数是如何传递的?这些问题都和函数栈帧有关系。
函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放:函数参数和函数返回值
临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)
保存上下文信息(包括在函数调用前后需要保持不变的寄存器)。

什么是栈?

栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函
数,没有局部变量,也就没有我们如今看到的所有的计算机语言。

在经典的计算机科学中,栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push),也可
以将已经压入栈中的数据弹出(出栈,pop),但是栈这个容器必须遵守一条规则:先入栈的数据后出
栈(First In Last Out, FIFO)。就像叠成一叠的术,先叠上去的书在最下面,因此要最后才能取出。

在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据
从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小。

在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。
在我们常见的i386或者x86-64下,栈顶由成为 esp 的寄存器进行定位的


要给大家讲明白这一点呢,就要给大家讲一下寄存器

什么是寄存器?

  • eax:通用寄存器,保留临时数据,常用于返回值
  • ebx:通用寄存器,保留临时数据
  • ebp:栈底寄存器
  • esp:栈顶寄存器
  • eip:指令寄存器,保存当前指令的下一条指令的地址

ebp

esp

这两个呢是今天的重点,要理解函数栈帧,就必须理解这两个寄存器。

ebp,esp中存放的是地址,这2个地址是用来维护函数栈帧的。

同时我们也要知道相关汇编命令

  • mov:数据转移指令
  • push:数据入栈,同时esp栈顶寄存器也要发生改变
  • pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变
  • sub:减法命令
  • add:加法命令
  • call:函数调用,1. 压入返回地址 2. 转入目标函数
  • jump:通过修改eip,转入目标函数,进行调用
  • ret:恢复返回地址,压入eip,类似pop eip命令

  函数栈帧的创建和销毁

预热知识准备:

在开始之前呢,再给大家补充一下知识:

 每一个函数调用,都要在栈区创建一个空间每个函数栈帧都有自己的 ebp 和 esp 来维护栈帧空间

  • push压栈:给栈顶放一个元素
  • pop出栈:从栈顶删除一个元素

我们知道每一个函数调用都会在栈区上开辟一块空间,

如果下面是高地址,上面是低地址

 这块空间由ebp esp所维护,在正在调用哪个函数,ebp和esp就去维护哪块函数空间的函数栈帧,比如说此时调用Add函数,那esp ebp就去维护Add函数空间的栈帧,通常我们把ebp叫做栈底指针,esp叫做栈顶指针。 

 函数的调用堆栈

 在vs2013中,main函数也是被其他函数调用的

__tmainCRTStartup

mainCRTStartup


 接下来我们继续:

转到反汇编

首先f10调试到main函数开始执行的第一行,右击鼠标转到反汇编。

#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
int main()
{
00C418B0  push        ebp  //把ebp寄存器中的值进行压栈
00C418B1  mov         ebp,esp   //move指令会把esp的值存放到ebp中,相当于产生了main函数的ebp,这个值就是invoke_main函数栈帧的esp    //sub会让esp中的地址减去一个16进制数字0xe4,产生新的esp
00C418B3  sub         esp,0E4h  
00C418B9  push        ebx  
00C418BA  push        esi  
00C418BB  push        edi  
//下面的代码是在初始化main函数的栈帧空间。
//1. 先把ebp-24h的地址,放在edi中
//2. 把9放在ecx中
//3. 把0xCCCCCCCC放在eax中
//4. 将从edp-0x2h到ebp这一段的内存的每个字节都改为cccccccc
00C418BC  lea         edi,[ebp-24h]  
00C418BF  mov         ecx,9  
00C418C4  mov         eax,0CCCCCCCCh  
00C418C9  rep stos    dword ptr es:[edi]  
00C418CB  mov         ecx,0C4C008h  
00C418D0  call        00C4131B  
	int a = 10;
00C418D5  mov         dword ptr [ebp-8],0Ah  
	int b = 20;
00C418DC  mov         dword ptr [ebp-14h],14h  
	int c = 0;
00C418E3  mov         dword ptr [ebp-20h],0  

	c = Add(a, b);
00C418EA  mov         eax,dword ptr [ebp-14h]  
00C418ED  push        eax  
00C418EE  mov         ecx,dword ptr [ebp-8]  
00C418F1  push        ecx  
00C418F2  call        00C410B4  
00C418F7  add         esp,8  
00C418FA  mov         dword ptr [ebp-20h],eax  

	printf("%d\n", c);
00C418FD  mov         eax,dword ptr [ebp-20h]  
00C41900  push        eax  
00C41901  push        0C47B30h  
00C41906  call        00C410D2  
00C4190B  add         esp,8  
	return 0;
00C4190E  xor         eax,eax  
}


函数栈帧的创建

 我们先来push压栈

操作前:

 我们看执行完:

这就是给大家讲的先把ebp压进去

ebp压进去以后再怎么办呢?


接下里是move

move是把esp给ebp

 


 下一步sub,sub是解码嘛

 我们注意到esp变了,这意味着什么呢?

esp的地址往上走了~


紧接着下来三次push ,其实是给顶上压进去了三个元素,这三个元素呢,我们不需要去管


 接下来lea

lea:load effective address

给edi里面放了一个地址

将从edp-0x2h到ebp这一段的内存的每个字节都改为cccccccc


补充说明:烫烫烫~

之所以程序输出“烫”这么一个奇怪的字,是因为main函数调用时,在栈区开辟的空间的其中每一
个字节都被初始化为cccccccc,而arr数组是一个未初始化的数组,恰好在这块空间上创建的,cccccccc的汉字编码就是“烫”,所以cccccccc被当作文本就是“烫”。


a,b,c创建:

	int a = 10;
00C418D5  mov         dword ptr [ebp-8],0Ah  //将10存储到ebp-8的地址处,ebp-8的位置其实就是a变量
	int b = 20;
00C418DC  mov         dword ptr [ebp-14h],14h  //将20存储到ebp-14h的地址处,ebp-14h的位置其实是b变量
	int c = 0;
00C418E3  mov         dword ptr [ebp-20h],0  //将0存储到ebp-20h的地址处,ebp-20h的位置其实是c变量

 以上汇编代码表示的变量a,b,c的创建和初始化,这就是局部的变量的创建和初始化 ,其实是局部变量的创建时在局部变量所在函数的栈帧空间中创建的


当a,b,c创建好后,我们就该:

调用Add函数:

c = Add(a, b);
00C418EA  mov         eax,dword ptr [ebp-14h]  //把b的值20放入eax
00C418ED  push        eax                      //栈顶压入
00C418EE  mov         ecx,dword ptr [ebp-8]    //把a的值10放入ecx
00C418F1  push        ecx                      //栈顶压入
00C418F2  call        00C410B4                 //调用
00C418F7  add         esp,8  
00C418FA  mov         dword ptr [ebp-20h],eax  

	printf("%d\n", c);
00C418FD  mov         eax,dword ptr [ebp-20h]  
00C41900  push        eax  
00C41901  push        0C47B30h  
00C41906  call        00C410D2  
00C4190B  add         esp,8  
	return 0;
00C4190E  xor         eax,eax  
}
00C41910  pop         edi  
00C41911  pop         esi  
00C41912  pop         ebx  
00C41913  add         esp,0E4h  
00C41919  cmp         ebp,esp  
00C4191B  call        00C41244  
00C41920  mov         esp,ebp  
00C41922  pop         ebp  
00C41923  ret  
00C418EA  mov         eax,dword ptr [ebp-14h]  //把b的值20放入eax
00C418ED  push        eax                      //栈顶压入
00C418EE  mov         ecx,dword ptr [ebp-8]  
00C418F1  push        ecx 

以上两个动作就是在进行传参

int Add(int x, int y)
{
00C41770  push        ebp  //将main函数栈帧的ebp保存,esp-4
00C41771  mov         ebp,esp  //将main函数的esp赋值给新的ebp,ebp现在是Add函数的ebp
00C41773  sub         esp,0CCh  //给esp-0xCC,求出Add函数的esp
00C41779  push        ebx  //将ebx的值压栈,esp-4
00C4177A  push        esi  //将esi的值压栈,esp-4
00C4177B  push        edi  //将edi的值压栈,esp-4
00C4177C  lea         edi,[ebp-0Ch]  
00C4177F  mov         ecx,3  
00C41784  mov         eax,0CCCCCCCCh  
00C41789  rep stos    dword ptr es:[edi]  
00C4178B  mov         ecx,0C4C008h  
00C41790  call        00C4131B  
	int z = 0;
00C41795  mov         dword ptr [ebp-8],0 //将0放在ebp-8的地址处,其实就是创建z 
	z = x + y;//接下来计算的是x+y,结果保存到z中
00C4179C  mov         eax,dword ptr [ebp+8]  //将ebp+8地址处的数字存储到eax中
00C4179F  add         eax,dword ptr [ebp+0Ch]  //将ebp+12地址处的数字加到eax寄存中
00C417A2  mov         dword ptr [ebp-8],eax  //将eax的结果保存到ebp-8的地址处,其实就是放到z中
	return z;
00C417A5  mov         eax,dword ptr [ebp-8]   //将ebp-8地址处的值放在eax中,其实就是把z的值存储到eax寄存器中,这里是想通过eax寄存器带回计算的结果,做函数的返回值。
}
00C417A8  pop         edi  
00C417A9  pop         esi  
00C417AA  pop         ebx  
00C417AB  add         esp,0CCh  
00C417B1  cmp         ebp,esp  
00C417B3  call        00C41244  
00C417B8  mov         esp,ebp  
00C417BA  pop         ebp  
00C417BB  ret

 图片中的 a’ 和 b’ 其实就是 Add 函数的形参 x , y 。这里的分析很好的说明了函数的传参过程,以及函数在进行值传递调用的时候,形参其实是实参的一份拷贝。对形参的修改不会影响实参。 


函数栈帧的销毁

函数调用要结束返回的时候,前面创建的函数栈帧也开始销毁。
那具体是怎么销毁的呢?我们看一下反汇编代码:

00C417A8  pop         edi  //在栈顶弹出一个值,存放到edi中,esp+4
00C417A9  pop         esi  //在栈顶弹出一个值,存放到esi中,esp+4
00C417AA  pop         ebx  //在栈顶弹出一个值,存放到ebx中,esp+4
00C417AB  add         esp,0CCh  //再将Add函数的ebp的值赋值给esp,相当于回收了Add函数的栈帧空间

 各位老铁们,本章知识到此就要说再见啦,我们接下来的知识,下一篇见,小赛毛与你不见不散!

加油啦,小比特~

记得一键三连嗷!三连!!三连!!!

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

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

相关文章

Flink DataStream API 介绍

Flink DataStream API 介绍 StreamExecutionEnvironment #mermaid-svg-JKeWa22W2vWA4zBS {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JKeWa22W2vWA4zBS .error-icon{fill:#552222;}#mermaid-svg-JKeWa22W2vWA4z…

使用element-ui实现树形穿梭框

<template><div class"transferTreeBox"><!-- 左侧待选内容 --><div class"SelectBox"><div class"boxTitle" click"clickAllSelect">全选 ></div><div class"boxCenter"><…

【Hack The Box】Linux练习-- Frolic

HTB 学习笔记 【Hack The Box】Linux练习-- Frolic &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年9月7日&#x1f334; &#x1f36d…

dumi 2,它来了它来了它来了

dumi 1.0 在 2020 年 3 月 2 日正式发布&#xff0c;到今天一共有 80 位 Contributor、提交 1100 Commit、为近 4000 个开源项目提供了组件库/站点的文档方案&#xff1b;dumi 作为一个 GitHub 数亿开源项目中的渺小一粒&#xff0c;能有这么多人共同参与、能为这么多项目提供价…

【JAVA程序设计】(C00097) 基于SSM的果树溯源可视化管理系统

基于SSM的果树溯源可视化管理系统项目简介项目获取开发环境项目技术运行截图项目简介 基于ssm框架的果树溯源可视化管理系统&#xff0c;本系统分为二种用户&#xff1a;管理员、农户 管理员角色包含以下功能&#xff1a; 登录、农户管理、商家管理、果树管理、地块管理、农资…

马上2023年了,终于发现一款颜值爆表的记账软件

不知道大家平时有没有记账的习惯&#xff0c;我是在疫情之后&#xff0c;才开始记账的。 记账之后&#xff0c;的确发现了很多问题。尤其是自己花钱大手大脚没有规划的毛病。 后来&#xff0c;在每个月第1周&#xff0c;我都会分析一下上一个月的账目&#xff0c;看看自己的收…

同花顺_代码解析_交易系统_J09_18

本文通过对同花顺中现成代码进行解析&#xff0c;用以了解同花顺相关策略设计的思想 目录 J_09 抛物线转向系统 J_10 均线系统 J_11 随机指标专家 J_12 顺势指标 J_15 动量线 J_16 心理线 J_17 变动速率 J_18 相对强弱指标 J_09 抛物线转向系统 指标标识由绿变红时为买…

LeetCode287之寻找重复数(相关话题:二分查找,快慢指针)

题目描述 给定一个包含 n 1 个整数的数组 nums &#xff0c;其数字都在 [1, n] 范围内&#xff08;包括 1 和 n&#xff09;&#xff0c;可知至少存在一个重复的整数。 假设 nums 只有 一个重复的整数 &#xff0c;返回 这个重复的数 。 你设计的解决方案必须 不修改 数组 …

【SIFT】超详详详解 - 实现细节记录

目录前言一、尺度空间的生成&#xff1a;高斯金字塔 Gaussian pyrmid1、图像的尺度空间 - 高斯金字塔 Gaussian pyramid2、高斯金字塔的组 与 组数 Octave1&#xff09;组2&#xff09;组数3&#xff09;升采样获得 base image3、高斯金字塔的层与层数 interval31&#xff09;层…

2022年轨道交通行业研究报告

第一章 行业概况 轨道交通是指运营车辆需要在特定轨道上行驶的一类交通工具或运输系统。最典型的轨道交通就是由传统火车和标准铁路所组成的铁路系统。随着火车和铁路技术的多元化发展&#xff0c;轨道交通呈现出越来越多的类型&#xff0c;不仅遍布于长距离的陆地运输&#x…

k8s基础命令及Linux上用Kubectl(k8s)部署Nginx

k8s基础命令及Linux上用Kubectl(k8s)部署Nginx 不懂K8s搭建的可以看我这篇文章 Linux上部署Kubectl(k8s) 1.k8s简介 1.1 Kubernetes 概念 在 k8s 上进行部署前&#xff0c;首先需要了解一个基本概念 Deployment Deployment 译名为 部署。在k8s中&#xff0c;通过发布 Depl…

积分商城小程序的作用_分享积分商城小程序开发的效果

积分商城系统带来的6点作用分别是&#xff1a;对商家的依赖性、提升转化和复购、运营模式多元化、提升收益、进行积分营销、进行口碑传播&#xff0c;下面我们就来详细的了解一下。 积分商城系统带来的作用一&#xff1a;对商家的依赖性 积分商城系统是进行积分兑换的&#xf…

渗透测试CTF-图片隐写的详细教程2(干货)

上篇文章我们介绍了这7个工具&#xff0c;这里简单的介绍一下。 Binwalk 用来检测图片中是否有隐藏的文件。 Foremost 将图片中的隐藏文件拆分出来。 010Editor ①修改图片的参数来查看隐藏信息。 ②查看压缩包是否是伪加密。 Stegsolve.jar 图片隐写查看神器。 OurSecret 1个图…

公众号免费查题功能搭建

公众号免费查题功能搭建 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#xf…

Word处理控件Aspose.Words功能演示:在 Java 中将文本转换为 PNG、JPEG 或 GIF 图像

在各种情况下&#xff0c;通常需要进行文本到图像的转换&#xff0c;例如&#xff0c;使文本成为只读。在上一篇文章中&#xff0c;我们写过如何将TXT文件中的文本转换为 Java 中的 PDF。在本文中&#xff0c;您将学习如何在 Java 中以编程方式将文本转换为PNG、JPEG或GIF图像。…

B. Catching Cheaters(最长公共子序列变形)

Problem - 1446B - Codeforces 给你两个字符串A和B&#xff0c;代表两个涉嫌作弊的学生的论文。对于任何两个字符串C&#xff0c;D&#xff0c;我们将其相似性分数S(C,D)定义为4⋅LCS(C,D)-|C|-|D|&#xff0c;其中LCS(C,D)表示字符串C和D的最长公共子序列。 你认为只有部分文…

三次握手与四次挥的问题,怎么回答?

在面试中&#xff0c;三次握手和四次挥手可以说是问的最频繁的一个知识点了&#xff0c;我相信大家也都看过很多关于三次握手与四次挥手的文章&#xff0c;今天的这篇文章&#xff0c;重点是围绕着面试&#xff0c;我们应该掌握哪些比较重要的点&#xff0c;哪些是比较被面试官…

大一学生网页课程作业 南京介绍网页设计 学生家乡网页设计作品静态 HTML网页模板源码 html我的家乡网页作业

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

uniapp自动识别并切换到pad端、pc端【不断更新】【伸手党福利】

目录uniapp自动切换到pad、pc端&#xff08;框架方法&#xff09;1. 新建文件&#xff1a;index为主页面&#xff08;代理页面&#xff09;detail为主页面的引用页面&#xff08;业务页面&#xff09;leftwindow为左边栏【名字随便起】topwindow为顶部栏【名字随便起】2. pages…

【LeetCode】808.分汤

题目描述 有 A 和 B 两种类型 的汤。一开始每种类型的汤有 n 毫升。有四种分配操作&#xff1a; 提供 100ml 的 汤A 和 0ml 的 汤B 。 提供 75ml 的 汤A 和 25ml 的 汤B 。 提供 50ml 的 汤A 和 50ml 的 汤B 。 提供 25ml 的 汤A 和 75ml 的 汤B 。 当我们把汤分配给某人之后&a…