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

文章目录

  • 本章主题:
  • 1. 什么是函数栈帧
  • 2. 理解函数栈帧能解决什么问题呢?
  • 3. 函数栈帧的创建和销毁解析
    • 3.1 什么是栈?
    • 3.2 认识相关寄存器和汇编指令
      • 相关寄存器
      • 相关汇编命令
    • 3.3 解析函数栈帧的创建和销毁
      • 3.3.1 预备知识
      • 3.3.2 函数的调用堆栈
      • 3.3.4 准备环境
      • 3.3.5 转到反汇编
      • 3.3.6 函数栈帧的创建
      • 3.3.7 函数栈帧的销毁
  • 总结

本章主题:

什么是函数栈帧?

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

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

1. 什么是函数栈帧

我们在写C语言代码的时候,经常会把一个独立的功能抽象为函数,所以C程序是以函数为基本单位的。那函数是如何调用的?函数的返回值又是如何带回的?函数参数是如何传递的?这些问题都和函数栈帧有关系。

函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放:

  • 函数参数和函数返回值
  • 临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)
  • 保存上下文信息(包括在函数调用前后需要保持不变的寄存器)。

2. 理解函数栈帧能解决什么问题呢?

理解函数栈帧有什么用呢?

只要理解了函数栈帧的创建和销毁,以下问题就能够很好的额理解了:

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

让我们一起走进函数栈帧的创建和销毁的过程中。

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

3.1 什么是栈?

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

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

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

在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。

在我们常见的i386或者x86-64下,栈顶由成为 esp 的寄存器进行定位。

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

相关寄存器

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

相关汇编命令

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

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

3.3.1 预备知识

首先我们达成一些预备知识才能有效的帮助我们理解,函数栈帧的创建和销毁。

  1. 每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。

  2. 这块空间的维护是使用了2个寄存器: espebpebp 记录的是栈底的地址, esp 记录的是栈顶的地址。

如图所示:

函数栈帧的创建和销毁讲解_第1张图片

  1. 函数栈帧的创建和销毁过程,在不同的编译器上实现的方法大同小异,本次演示以VS2019为例。

3.3.2 函数的调用堆栈

演示代码:

#include  

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; 
}

这段代码,如果我们在VS2019编译器上调试,调试进入Add函数后,我们就可以观察到函数的调用堆栈(右击勾选【显示外部代码】),如下图:

函数栈帧的创建和销毁讲解_第2张图片

函数调用堆栈是反馈函数调用逻辑的,那我们可以清晰的观察到, main 函数调用之前,是由 invoke_main 函数来调用main函数。

invoke_main 函数之前的函数调用我们就暂时不考虑了。

那我们可以

你可能感兴趣的:(c语言,开发语言,后端)