返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C语言超详细解析函数栈帧
  • 859
分享到

C语言超详细解析函数栈帧

2024-04-02 19:04:59 859人浏览 八月长安
摘要

目录一、前面二、预备知识三、栈帧创建与销毁四、总结一、前面 本章将以汇编视角看函数栈帧的内存是如何使用与回收的,为了降低汇编语言的理解成本,以图示的方式讲解每一步汇编指令所带来的效果

一、前面

本章将以汇编视角看函数栈帧的内存是如何使用与回收的,为了降低汇编语言的理解成本,以图示的方式讲解每一步汇编指令所带来的效果,来逐步展示函数栈帧的形成与销毁的整个过程。

展示环境:win10 && vs2019

二、预备知识

这些预备知识理解与否对本篇文章并无很大关系,之所以预备这些知识是为了让读者能够更加相信函数栈帧的形成与销毁过程就是如此。

栈区:内存四区之一,内存为了使用和管理,被划分为四部分,其中栈区就是内存被划分的区域之一,栈的使用习惯是,先使用高地址部分,在使用底地址部分。

函数栈帧:即在调用函数时,为函数开辟的一块内存空间,由于该内存空间在栈区,因此该空间被称作函数栈帧,简称栈帧。

栈顶:故名思意,就是栈的顶部,更确切的说是指向存放在栈区数据的顶部。

栈底:栈的底部。

寄存器:寄存器cpu内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。简单来说就是独立于内存,用来存储少量数据的器件。

ebp:栈底指针寄存器

esp:栈底指针寄存器

其它寄存器:ebx、esi、edi、ecx、eax

入栈(压栈):先将栈顶指针向上移动四字节的大小空间,再将寄存器的数据放入那四字节空间。这里的向上移动是指向低地址处移动。

入栈指令:push a。

图解:以push a为例。

 出栈:将栈顶指针向下移动四字节,这里的向下是往低地址处移动四个字节的空间。并将这四个字节的数据放入某个寄存器中。

出栈指令:pop a。

图解:以pop  a为例。

简单汇编操作指令

mov a b:将b赋值给a,C语言表示就是a=b。

sub a b:将a-b的结果赋值给a,c语言表述就是a=a-b。

add a b :将a+b的结果赋值给a,c语言表述就是a=a+b。

由于理解成本的原因,遇到的其它汇编指令本文会直接指出它的作用效果。

三、栈帧创建与销毁

以Add函数调用为例


#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
	int z = x + y;
	return z;
}
int main()
{
	int a = 10;
	int b = 20;
	int z = 0;
	z = Add(a, b);
	printf("%d\n", z);
	return 0;
}

该代码对应的汇编指令如下:

 需要说明的是,main函数也被别的函数调用的,调用关系是:__mainCRTStartup调用main函数,mainCRTStartup函数调用__mainCRTStartup。

再调用main函数之前,栈区是这样的。

 指令分说:


int main()
{
00F71E40  push        ebp  
00F71E41  mov         ebp,esp  
00F71E43  sub         esp,0E4h  

以上图为参照。

第一条指令:将寄存器ebp的值压栈

第二条指令:将寄存器esp的值赋值给ebp

第三条指令:将esp-0E4h赋值给寄存器esp,形象的表述是esp向低地址方向移动4个字节,上端为低地址,下端为高地址,即向上移动4字节空间。

栈区视图变为:

 这三条指令,简单来说就是为main函数在栈区开辟了一块空间(这块空间大小系统会帮我们自动开辟好。)

指令分说:

00F71E49 push ebx
00F71E4A push esi
00F71E4B push edi

将三个寄存器的值压入栈中

栈区视图变为:

指令分说:

00F71E4C lea edi,[ebp-24h]
00F71E4F mov ecx,9
00F71E54 mov eax,0CCCCCCCCh
00F71E59 rep stos dWord ptr es:[edi]

这四条指令我们就解读了,效果就是将main函数的栈帧空间以16进制值cccccccc填充。

栈区视图变为:

指令分说:

00F71E5B mov ecx,0F7C003h
00F71E60 call 00F7130C

这两条指令是编译器检查用的,初学不必花费更多时间了解更细节的部分。

vs2013没有这一检查部分,vs2019检查很严格。

指令分说:


int a = 10;
00F71E65  mov         dword ptr [ebp-8],0Ah  
	int b = 20;
00F71E6C  mov         dword ptr [ebp-14h],14h  
	int z = 0;
00F71E73  mov         dword ptr [ebp-20h],0  

第一条汇编指令:将0Ah放入[ ebp-8 ]这块空间中,即把a放入那块空间。

第二条汇编指令:将14h放入[ ebp-14h ]这块空间中,即把b放入那块空间中。

第三条汇编指令:将0放入[ ebp-20h ]这块空间中。即把z放入那块空间中。

栈区图示:

 简单来说:就是将局部变量放入对应的函数栈帧中。

指令分说:

z = Add(a, b);
00F71E7A mov eax,dword ptr [ebp-14h]
00F71E7D push eax
00F71E7E mov ecx,dword ptr [ebp-8]
00F71E81 push ecx

第一条指令:将【ebp-20】这块空间4字节的数据放入eax中。即把b=20的数据放入eax中。

第二条指令:将eax的数据压栈。

第三条指令:将【ebp-8】这块空间4字节的数据放入ecx中。即把a=10的数据放入ecx中。

第四条指令:将ecx的数据压栈。

栈区视图:

 这里的20和10,就是我们传过去的实参,之后Add函数调用的x和y就是指这两块空间。

那么我们可以知道:函数传参是从右向左传的。这里就是先传的b再传的a。

指令分说:

00F71E82 call 00F710B4

调用的函数:
int Add(int x, int y)
{
00F71740 push ebp
00F71741 mov ebp,esp
00F71743 sub esp,0CCh
00F71749 push ebx
00F7174A push esi
00F7174B push edi
00F7174C lea edi,[ebp-0Ch]
00F7174F mov ecx,3
00F71754 mov eax,0CCCCCCCCh
00F71759 rep stos dword ptr es:[edi]
00F7175B mov ecx,0F7C003h
00F71760 call 00F7130C
int z = x + y;
00F71765 mov eax,dword ptr [ebp+8]
00F71768 add eax,dword ptr [ebp+0Ch]
00F7176B mov dword ptr [ebp-8],eax
return z;
00F7176E mov eax,dword ptr [ebp-8]
}
00F71771 pop edi
00F71772 pop esi
00F71773 pop ebx
00F71774 add esp,0CCh
00F7177A cmp ebp,esp
00F7177C call 00F71235
00F71781 mov esp,ebp
00F71783 pop ebp
00F71784 ret

第一条汇编指令:call是调用指令,调用Add函数。

经过上次的指令,这里我就直接介绍效果了。

00F71740 push ebp
00F71741 mov ebp,esp
00F71743 sub esp,0CCh

这三条指令,为Add函数在栈区开辟对应的空间大小。

栈区图示:

00F71749 push ebx
00F7174A push esi
00F7174B push edi

将ebx,esi,edi入栈。

图示: 

00F7174C lea edi,[ebp-0Ch]
00F7174F mov ecx,3
00F71754 mov eax,0CCCCCCCCh
00F71759 rep stos dword ptr es:[edi]

对Add函数栈帧做初始化,将里面的数据置换为cccccccc。(用于初始化栈帧的具体数值取决于编译器)

00F7175B mov ecx,0F7C003h
00F71760 call 00F7130C

编译器做的检查,不必理会。

int z = x + y;
00F71765 mov eax,dword ptr [ebp+8]
00F71768 add eax,dword ptr [ebp+0Ch]
00F7176B mov dword ptr [ebp-8],eax

取[ebp+8]空间的数据放入eax中

取 [ebp+0Ch]  与eax的数据相加后放入eax中。

将eax的值放入ptr [ebp-8]中。

图示:

return z;
00F7176E mov eax,dword ptr [ebp-8]

返回时,通过寄存器的方式,将返回值交给寄存器。

00F71771 pop edi
00F71772 pop esi
00F71773 pop ebx
00F71774 add esp,0CCh
00F7177A cmp ebp,esp
00F7177C call 00F71235
00F71781 mov esp,ebp
00F71783 pop ebp
00F71784 ret

代码分说:

00F71771 pop edi
00F71772 pop esi
00F71773 pop ebx

将edi、esi、ebx出栈

图示: 

00F71774 add esp,0CCh

00F7177A cmp ebp,esp
00F7177C call 00F71235
00F71781 mov esp,ebp
00F71783 pop ebp

0CCh是Add函数栈帧的大小

所以esp向下移动到dbp的位置。

 之后pop ebp,由于栈顶指向的是main函数栈帧的栈底,因此出栈ebp指向main函数栈帧的栈底。

图示:

调用Add返回之后,继续执行以下指令。

00A717F7 add esp,8
00A717FA mov dword ptr [ebp-20h],eax
return 0;
00A717FD xor eax,eax
}
00A717FF pop edi
00A71800 pop esi
00A71801 pop ebx
00A71802 add esp,0E4h
00A71808 cmp ebp,esp
00A7180A call 00A71235
00A7180F mov esp,ebp
00A71811 pop ebp
00A71812 ret

第一条指令:将esp向下移动8个字节,即销毁x和y这两块连续的形参。

第二条指令:将寄存器eax保存的Add函数的返回值交给z。

图示:

 之后的指令就是回收main函数的栈帧了,回收过程都差不多,就不细细讲解了。

四、总结

以下函数调用为例。


#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
	int z = x + y;
	return z;
}
int main()
{
	int a = 10;
	int b = 20;
	int z = 0;
	z = Add(a, b);
	return 0;
}

初始:mainCRTStartup函数调用__mainCRTStartup、__mainCRTStartup调用main函数。

栈区上先为以上两个函数分配函数栈帧。

调用main函数时,为main函数分配函数栈帧(该大小是自动开辟的)开辟好空间后,用cccccccc数值填充main函数栈帧。(具体用什么数值初始化函数栈帧取决与编译器)。

执行到int a = 10时,将局部变量a的值放入main函数栈帧的某块空间中,int b =20、int z=0也是如此,它们的空间都在main函数的函数栈帧中。

当执行到z=Add(a,b)时。

先传参,传参顺序是从右向左,所有先将b压入栈中,在将a压入栈中。

这两块空间就是y和、x。注意y和x并不在Add函数栈帧中,而是在main函数栈帧和Add函数栈帧之间的一块独立的空间。

然后为Add函数开辟函数栈帧,并ccccccc数值填充Add函数栈帧。(具体用什么数值初始化函数栈帧取决与编译器)。

当执行到z=x+y时,在Add函数栈帧中取一块空间作为局部变量z使用,在取出y和x空间的值,放入z中。(z是在Add函数栈帧中的)。

当执行到return z时,将z的值放入寄存器中。

之后再销毁Add函数的栈帧、销毁形参x和y、将寄存器的值交给z。

之后销毁main函数也是如此。

这里的销毁不是将Add函数的栈帧数据置为0或者其他数,它里面的数据并不是直接丢失的,而是直接告诉操作系统,这块空间我不需要了,Add函数栈帧里的数据还是存在的,只不过当你调用新函数时,Add函数栈帧这块空间会被新函数占用,并初始化为cccccccc这样的数值,那么Add函数栈帧空间数据也就丢失了。

到此这篇关于C语言超详细解析函数栈帧的文章就介绍到这了,更多相关C语言 函数栈帧内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C语言超详细解析函数栈帧

本文链接: https://lsjlt.com/news/144374.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • C语言超详细解析函数栈帧
    目录一、前面二、预备知识三、栈帧创建与销毁四、总结一、前面 本章将以汇编视角看函数栈帧的内存是如何使用与回收的,为了降低汇编语言的理解成本,以图示的方式讲解每一步汇编指令所带来的效果...
    99+
    2024-04-02
  • 详细理解函C语言的函数栈帧
    目录一、函数栈帧的创建1.寄存器2.函数栈帧3.函数中调用函数二、函数栈帧的销毁总结一、函数栈帧的创建 1.寄存器 一般来说,计算机中的寄存器有六种 分别是:eax, ebx, e...
    99+
    2024-04-02
  • C语言函数栈帧解析
    目录一、什么是函数栈帧 1.寄存器:2.函数栈帧 3.栈帧的作用和维护 4.栈帧结构二、函数栈帧的创建1.汇编代码2.main函数 2.栈帧创...
    99+
    2024-04-02
  • C语言函数栈帧详解
    目录前言一.函数栈帧是什么?二、栈帧准备知识1.内存分区2.什么是栈?三、详解栈帧创建与销毁全过程调用函数之前:将传入函数的值放入栈中函数执行:1.保护当前ebp2.创建所需调用函数...
    99+
    2024-04-02
  • C语言超详细讲解函数栈帧的创建和销毁
    目录1、本节目标2、相关寄存器3、相关汇编指令4、什么是函数栈帧5、什么是调用堆栈6、函数栈帧的创建和销毁(1)、main函数栈帧的创建与初始化(2)、main函数的核心代码(3)、...
    99+
    2024-04-02
  • 怎么理解C语言的函数栈帧
    本篇内容介绍了“怎么理解C语言的函数栈帧”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、函数栈帧的创建1.寄存器一般来说,计算机中的寄存器...
    99+
    2023-06-25
  • C语言函数栈帧的创建和销毁详解
    目录写在前面Add函数的调用函数传参Add函数栈帧的创建Add函数栈帧的销毁main函数栈帧的销毁总结写在前面 我们知道,每一次函数调用都需要在栈区上为其开辟一块空间,这块空间就叫做...
    99+
    2024-04-02
  • C语言函数栈帧的创建与销毁详解
    目录前言一、函数栈帧是什么?1.寄存器2.ebp与esp二、函数栈帧的创建1.代码块2.调用堆栈3.esp与ebp如何维护栈帧总结 前言 大家在学习的时候一定有以下困惑: ...
    99+
    2024-04-02
  • C语言超详细讲解库函数
    目录1 返回整数的getchar函数2 更新顺序文件3 缓冲输出与内存分配4 库函数练习1 返回整数的getchar函数 代码: #include<stdio.h> ...
    99+
    2024-04-02
  • c语言函数栈帧的创建和销毁过程详解
    目录1 相关知识介绍 1.1 寄存器1.2 函数栈帧概述2 栈帧创建与销毁过程1 相关知识介绍  1.1 寄存器 一般计算机内通用寄存器包括eax,ebx,ec...
    99+
    2024-04-02
  • C语言函数超详细讲解上篇
    目录前言1、函数是什么?2、C语言中函数的分类2.1 库函数2.1.1 如何学会使用库函数2.1.2 自定义函数3、函数的参数3.1 实际参数(实参)3.2 形式参数(形参)4、函数...
    99+
    2024-04-02
  • C语言函数超详细讲解下篇
    目录前言函数的声明和定义函数声明函数定义举例简单的求和函数把加法单独改写成函数添加函数声明带头文件和函数声明静态库(.lib)的生成静态库文件的使用方法函数递归什么是递归?递归的两个...
    99+
    2024-04-02
  • C语言详尽图解函数栈帧的创建和销毁实现
    目录常见寄存器基本的汇编语言知识具体实现关于栈帧创建与销毁的问答题注:本文章所使用的编译器是VS2010,由于不同编译器的函数栈帧与销毁略有差异,所以具体细节请读者自行实践! 常见寄...
    99+
    2024-04-02
  • C语言进阶栈帧示例详解教程
    目录正片开始栈有什么用?寄存器main函数创建局部变量创建函数部分形参与实参正片开始 今天来讲讲我对栈帧创建与销毁的拙见。理解什么是栈帧首先知道什么是栈: 在数据结构中, 栈是限定仅...
    99+
    2024-04-02
  • C++函数栈帧的示例分析
    这篇文章将为大家详细讲解有关C++函数栈帧的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一、什么是函数栈帧每一次函数调用都是一个过程,为函数开辟栈空间,用于本次函数调用中临时变量的保存、现场保护...
    99+
    2023-06-20
  • C++超详细讲解析构函数
    目录特性析构函数处理自定义类型编译器生成的默认析构函数特性 析构函数是特殊的成员函数 特征如下: 析构函数名是~类名;无参数无返回值;一个类有且只有一个析构函数;对象声明周期结束,编...
    99+
    2024-04-02
  • C语言超详细讲解栈的实现及代码
    目录前言栈的概念栈的结构栈的实现创建栈结构初始化栈销毁栈入栈出栈获取栈顶元素获取栈中有效元素个数检测栈是否为空总代码Stack.h 文件Stack.c 文件Test.c 文件前言 栈...
    99+
    2024-04-02
  • C语言函数栈帧如何创建和销毁
    这篇文章主要为大家展示了“C语言函数栈帧如何创建和销毁”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C语言函数栈帧如何创建和销毁”这篇文章吧。写在前面我们知道,每一次函数调用都需要在栈区上为其开...
    99+
    2023-06-29
  • C语言超详细讲解getchar函数的使用
    目录一、getchar 函数二、缓冲区1、什么是缓冲区2、为什么要存在缓冲区3、缓冲区的类型4、缓冲区的刷新三、getchar 函数的正确使用1、getchar 的换行问题2、get...
    99+
    2024-04-02
  • 深入理解C++函数栈帧
    目录一、什么是函数栈帧二、具体原理2.1 main函数的调用2.2 sum函数的调用参考:一、什么是函数栈帧 每一次函数调用都是一个过程,为函数开辟栈空间,用于本次函数调用中临时变量...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作