函数栈帧的创建和销毁

本文详细解析了函数栈帧的定义和作用,包括存储函数参数、局部变量和上下文信息。通过一个C语言的示例,解释了函数调用时栈帧的创建、参数传递、返回值处理以及函数调用的汇编指令。阐述了main函数调用Add函数的过程,展示了栈帧如何在不同函数间动态变化,以及数据如何在寄存器和栈中移动。
摘要由CSDN通过智能技术生成

一.什么是函数栈帧?

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

1.函数参数和函数返回值。

2.临时变量(包括函数的非静态的局部变量以及编译器自动生产的其他临时变量)。

3.保存上下文信息(包括在函数调用前后需要保持不变的寄存器)。

预备知识:        

1.首先在解析之前,我们要知道栈的一些相关知识。

栈(stack)是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今看到的所有的计算机语言。在经典的计算机科学中,栈被定义为一种特殊的容器

用户可以将数据压入栈中(入栈,push),也可以将已经压入栈中的数据弹出(出栈,pop),但是栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out, FIFO)。就像叠成一叠的术,先叠上去的书在最下面,因此要最后才能取出。 在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据 从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小。(先进后出,后进先出)。在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。在我们常见的i386或者x86-64下,栈顶由成为 esp 的寄存器进行定位的

2.其次,我们要知道寄存器和汇编指令的一些知识。

a.相关的寄存器:  :不同的编译器或者不同的版本所用的代号不同(但是汇编代码的大致思路都一样。)

eax:通用寄存器,保留临时数据(常用于放函数return的返回值)

ebx:通用寄存器,保留临时数据

ecx,edx:通用寄存器,保留临时数据

ebp:栈底寄存器(存的是地址,相当于一个指针指向我们需要访问的空间位置)

esp:栈顶寄存器(存的是地址,相当于一个指针指向我们需要访问的空间位置)

rip:指令寄存器,保存当前指令的下一条指令的地址

b.相关的汇编指令:

mov:数据转移指令

push:数据入栈,同时esp栈顶寄存器也要发生改变

pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变

sub:减法命令

add:加法命令

call:函数调用,1. 压入返回地址 2. 转入目标函数

jump:通过修改eip,转入目标函数,进行调用

ret:恢复返回地址,压入eip,类似pop eip命令

分析:利用一个例子如下:

#include<stdio.h>

int Add(int x, int y)
{
   int z=0;
   z=x+y;
   return z;
}
int main()//在main函数中调用实现两个数相加的一个函数
{
  int a=10;
  int b=20;
  int c=0;
  c=Add(a,b);
  printf("%d\n",c);

   return 0;
}

首先,main函数在vs2022中也是被其他函数调用的叫做invoke_main函数。此时会生成一个invke_main的栈帧空间,栈顶指针esp指向低地址的栈帧空间的最上面的位置,而栈底指针ebp指向高地址的栈帧空间的最下面的位置。

然后,同理,在进入main函数后,在创建我们的局部变量a,b,c之前,会执行我们的汇编指令,用push,sub等相关指令,改变esp和ebp指针的指向,创建新的main函数的栈帧空间。之后还是在main函数的栈帧空间利用mov等相关汇编指令,改变esp和ebp指针的指向,找到合适空间创建局部变量a,b,c。

之后,调用Add函数,在进入Add函数之前,会将我们的参数a,b的值放入我们的ecx,edx寄存器中(这里我们也可以看出传参时,形参只是我们的实参的拷贝,我们再函数内部改变形参的值,并不会反过来改变我们的实参),c放入eax寄存器中。随后进入Add函数,通过mov汇编指令,找到ecx,edx,存的a,b的值。随后还是和之前一样,开辟一个Add函数的栈帧空间,在Add的函数栈帧空间中创建新的局部变量z,通过指针把b的值放到eax中,再把a的值放到ecx中,通过add算出的ecx加eax的值放到eax中,把eax的值通过指针找到z的地址,放入z的变量中。执行return时,为了防止调用完函数,函数栈帧被销毁,里面的数据也消失。所以我们要把返回值再装进eax寄存器中。之后通过pop汇编指令,出栈销毁。

紧接着,我们从Add函数中出来,Add创建的函数栈帧被销毁,再一次回到main函数的栈帧空间,把eax中的返回值,通过指针找到我们c变量的地址,并存入c中,这样就得到了我们的两个数的和。直到我们main函数执行完之后,也通过pop,出栈销毁。

 

大致的过程图表示:下图是再vs2022中运行得到的,可能代号和上述有差别。

进入mian函数,创建一个main函数的栈帧空间:

 在main函数中创建临时变量a,b,c,并调用Add函数,把a,b传参到edx,ecx寄存器中:

进入Add函数,实现加法运算,并把返回值存入z中,在函数栈帧销毁前,把z的值存入寄存器eax随后把Add的函数栈帧空间销毁。

 执行完Add函数后,再回到main函数的栈帧空间中,把eax寄存器中的值存入c中并打印。最后把main函数执行完后,并把main函数的函数栈帧空间销毁。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值