函数帧栈的创建和销毁(一)

发布时间:2023年12月21日

目录

什么是函数栈帧

理解函数栈帧能解决什么问题

函数栈帧的创建和销毁

什么是栈

认识相关寄存器和汇编指令

相关寄存器

相关汇编命令

esp和ebp?

解析函数栈帧的创建和销毁

学前补充

函数的调用堆栈 ?


什么是函数栈帧

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

理解函数栈帧能解决什么问题

  1. 局部变量是如何创建的?
  2. 为什么局部变量不初始化内容是随机的?
  3. 函数调用时参数时如何传递的?传参的顺序是怎样的?
  4. 函数的形参和实参分别是怎样实例化的?
  5. 函数的返回值是如何带会的?

函数栈帧的创建和销毁

什么是栈

? ???????栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今看到的所有的计算机语言。
????????在经典的计算机科学中,栈被定义为一种特殊的容器,用户可以将数据压入栈中(入栈,push ),也可将已经压入栈中的数据弹出(出栈,pop ),但是栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out FIFO )。
????????就像叠成一叠的书,先叠上去的书在最下面,因此要最后才能取出。在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小。 在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。 在我们常见的i386 或者 x86-64 下,栈顶由成为 esp 的寄存器进行定位的。

认识相关寄存器和汇编指令

相关寄存器

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

相关汇编命令

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

esp和ebp寄存器

????????当函数调用发生时,栈上会为新的函数分配一块新的空间,这个空间通常被称为栈帧(stack frame)。在维护帧栈时,`ebp` 和 `esp` 寄存器通常会指向当前活动函数的栈帧底部和顶部。

????????对于第一个空间(即当前活动函数的栈帧),`ebp` 通常会指向该空间的底部地址。而 `esp` 则是指向该空间顶部地址之上一个位置。也就是说,在只有一个空间时,它们都指向同一位置。

????????当发生另一个函数调用并创建了新的栈帧后,旧的 `ebp` 的值将被保存到新创建的栈帧中,并且更新为指向新创建栈帧底部地址。此时,早期创建出来但尚未完成执行(即尚未返回)的那个函数所对应之前保留在堆上内存中已经存在了两份数据:一份是原始状态下保存着早期生成代码段信息、静态变量等数据;另外一份则是现场保护压入堆盏信息以及局部变量等数据。

因此,在这种情况下:

  • 早期生成代码段、静态变量等仍然可以通过原始 `ebp+偏移量1/2/3...n*4字节大小(32位系统)` 的方式访问
  • 新创建的栈帧中的局部变量等可以通过新 `ebp+偏移量1/2/3...n*4字节大小(32位系统)` 的方式访问

结论:ebp和 esp?寄存器在维护帧栈时会指向当前活动函数的栈帧底部和顶部

我们也可以将两个寄存器叫做栈顶指针和栈底指针

解析函数栈帧的创建和销毁

学前补充

1. 每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。
2. 使用2个寄存器esp和ebp维护函数帧栈空间?, ebp记录栈底地址, esp记录栈顶地址

3. 函数栈帧的创建和销毁过程,在不同的编译器上实现的方法大同小异

函数的调用堆栈 ?

#include <stdio.h>

int Add(int x, int y)
    {
        int z = 0;
        z = x + y;
        return z;
    }
int main()
{
    int a = 3;
    int b = 5;
    int ret = 0;
    ret = Add(a, b);
    printf("%d\n", ret);
    return 0;
}

????????打开调试窗口的调用堆栈,发现在右侧的监视窗口中出现了__taminCRTStartup(),它表示在执行main函数时调用了__taminCRTStartup()这个堆栈,这就证明main也是被调用的,它在__taminCRTStartup()函数中被调用,此外,在监视窗口中还有一个函数mainCRTStartup(),__taminCRTStartup函数是在该函数中被调用的:

结论:main函数也是被其它函数调用的,当调用main函数之前就已经申请调用了两块空间,在mian函数中调用Add函数时,又会在main函数空间的上层为Add函数开辟一块空间,此时原本用于维护main函数的esp、ebp寄存器就会去维护Add函数栈帧的顶部和底部

~over~

文章来源:https://blog.csdn.net/m0_73975164/article/details/135119352
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。