博客引用相关文献:
1.《程序员的自我修养——链接、装载与库》
2.西安比特教育科技.《C语言进阶_动态内存管理》
👻内容专栏:《C/C++学习专栏》
🐨本文概括:讲解函数栈帧创建与销毁的具体过程
🐼本文作者:花 碟
🐸发布时间:2023.4.19
知识点回顾
前期我们学习的时候,对许多知识可能有很多困惑。
比如:
局部变量是怎么创建的?
为什么局部变量创建后默认是随机值?
函数是怎么传参的?传参的顺序是怎么样的?
形参和实参的关系是怎么样的?
函数调用是怎么做的?
函数调用结束后是怎么返回的?
还弄不清?没关系,相信聪明的小伙伴们学习此篇章的函数栈帧知识,以上问题就会迎刃而解啦!
OK,让我们来揭开函数栈帧的创建与销毁的神秘面纱吧~~
一、什么是栈帧(堆栈帧)?
在了解函数栈帧之前,我们不得不先了解一下内存布局、寄存器、汇编指令相关概念。
1.内存布局
C/C++内存布局中的几个区域:
1.栈区(stack): 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。栈是向下增长的。栈区是向下生长的,也就是说,栈顶的地址是最小的。栈区的大小是有限制的,如果超出了栈的大小,就会发生栈溢出错误。
2.堆区(heap):堆区是由程序员手动分配和释放的内存区域,用于存储动态分配的内存。堆区是向上生长的,也就是说,堆顶的地址是最大的。堆区的大小是没有限制的,但是如果没有及时释放内存,就会导致内存泄漏。
3.数据段(静态区static):存放全局变量、静态数据。程序结束后由系统释放。
4.代码段:存放函数体(类成员函数和全局函数)的二进制代码。
2.常用寄存器
eax、ebx、ecx、edx、esp、ebp是x86架构CPU中比较常见的寄存器,它们的含义和作用如下:
eax寄存器:又称为累加器寄存器(Accumulator Register),通常用于存储算术计算的操作数和结果。在函数调用时,EAX寄存器也用于保存函数返回值。
ebx寄存器:又称为基址寄存器(Base Register),通常用于存储内存地址信息。在处理数据时,EBX寄存器可以作为一个指针来访问内存。
ecx寄存器:又称为计数器寄存器(Counter Register),通常在循环中使用,用于对循环次数进行计数,并控制循环的结束。
edx寄存器:又称为数据寄存器(Data Register),通常用来存储数据,例如两个操作数的乘积,以及一些特定系统调用的参数。
esp寄存器:又称为栈指针寄存器(Stack Pointer Register),用于指向当前栈顶的位置,也就是最后压入栈中的数据的地址。在函数调用时,ESP寄存器也用于保存当前函数的栈帧信息。
ebp寄存器:又称为基址指针寄存器(Base Pointer Register),用于指向当前栈帧的基地址,也就是当前函数栈帧在栈中的起始位置。在函数调用时,EBP寄存器可以用来定位本地变量和函数参数。
esi寄存器:又称为源索引寄存器(Source Index Register),通常用于存放源数据地址,在计算机复制、移动、传输等操作中扮演着重要角色。
edi寄存器:又称为目标索引寄存器(Destination Index Register),通常用于存放目标数据地址,在计算机复制、移动、传输等操作中也扮演着重要角色
3.汇编指令
MOV:将数据从一个地方移动到另一个地方,例如将一个寄存器中的值移动到另一个寄存器、内存地址或立即数中。
ADD/SUB:加法和减法指令,可以将两个操作数相加或相减,并将结果保存在目标寄存器或内存位置中。
CMP:比较两个操作数并设置标志位,用于支持条件跳转等操作。
JMP:无条件跳转指令,跳转到指定的代码位置执行。
CALL/RET:用于函数的调用与返回,CALL指令将当前程序计数器(PC)入栈并跳转到指定位置,RET指令从堆栈中弹出PC并跳转回调用函数的位置。
PUSH/POP:用于堆栈操作,PUSH指令将数据压入堆栈顶,POP指令将数据从堆栈顶弹出。
NOP:空指令,不执行任何操作,通常用于占位或调试。
XOR/OR/AND:逻辑运算指令,XOR进行异或运算,OR进行或运算,AND进行与运算。
👇👇对于栈的详细介绍 :
🏷️在经典的计算机科学中,栈被定义为一个特殊的容器,用户可以将数据压入栈中(简称:压栈,push),也可以将已经压入栈中的数据弹出(简称:出栈,pop),但栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out,FIFO),多多少少像叠成一摞的书籍📚:先叠上去的书在最下面,因此要最后才能取出。
🏷️ 栈是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而出栈操作使栈减小。
👇👇函数栈帧的介绍:
esp(Extended Stack Pointer)为扩展栈指针寄存器,是指针寄存器的一种,用于存放函数栈顶指针。与之对应的是ebp(Extended Base Pointer),扩展基址指针寄存器,也被称为帧指针寄存器,用于存放函数栈底指针。
我们知道,在操作系统里,栈总是向下增长的(高地址向低地址增长)。在i386下,栈顶由esp的寄存器进行定位。压栈的操作使栈顶的地址减小,弹出的操作使栈顶地址增大。观察下方描述图,这里栈底指针的地址是0xbfffffff,而esp栈顶指针标明了栈顶,地址为0xbffffff4。在栈上压入数据会导致esp减小,弹出数据使得esp增大。相反,直接减小esp的值也等效于在栈上开辟空间,直接增大esp的值等价于在栈上回收空间。栈保存了一个函数调用所需要的维护信息,通常由esp以及ebp两个寄存器来维护,常被称为栈帧(Stack Frame)