关于栈帧

一:栈帧叫活动记录,是编译器用来实现函数调用的一种数据结构。

也可以说栈帧就是存储在用户栈上(内核栈)每一次函数调用涉及的相关信息的记录单元。

二:对栈的了解(用户栈和内核栈)

栈作为一种特殊的数据结构而存在(和“队列”相反的记录结构和操作规则),是一种只能在一端进行插入和删除操作的特殊线性表。

栈按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)

栈有很多自己的特性,它具有记忆功能,对栈的插入与删除操作中,不需要改变栈底指针;而是栈是从高地址向低地址延伸的。每个函数的每次调用,都有他自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。因此栈的作用就是用来保持栈帧的活动记录(函数调用)。

对于一个栈来说,寄存器ebp和esp分别指向系统最上面一个栈帧的底部和栈帧顶部(实际上也是栈的顶部)。

三:对栈帧的了解

栈帧表示程序的函数调用记录,而栈帧又是记录在栈上面,很明显栈上保持了N个栈帧的实体,可以说栈帧将栈分割成了N个记录块,但是这些记录块大小不是固定的,因为栈帧不仅保存诸如:函数入参,出参,返回地址和上一个栈帧的栈底指针等信息,还保存了函数内部的自动变量(甚至可以是动态分布内存,allocal函数就可以实现,但在某些系统中不行),因此,不是所有的栈帧的大小都相同。

四:通过一个实例了解栈帧

void func(int m,int n)

{

int a,b;

a = m;

b = n;

}

main()

{

.....

func(m,n);

L:下一条语句

......

}

上面是一个简单的可执行代码,一个可执行程序的开始嵌入了启动例程代码,在执行的由启动例程调用main函数,可以说main函数是第一个被调用的C代码函数,暂且认为是main函数是第一函数。

这里的main函数只是简单的调用了一个函数func,那么在main调用func函数前,栈的情况是下面这个样子:


此时栈中只有一个main函数的栈帧,从低地址esp(栈顶指针)到高地址ebp(栈帧栈底指针)的这块区域,就是当前main函数的栈帧。当main中调用func时。写成汇编大致是:

push m

push n;俩个参数压入栈

call func;调用func,将返回地址(实际上是当前PC值得下一个值)填入栈,并跳转到func


当成功跳转到func函数中,func函数的栈帧就已经形成了,但是形成新的栈帧之前必须要重新记录当前的栈帧的栈底指针ebp,下面几个动作被系统自动加入:

_func:

push ebp;

mov ebp,esp;上一栈帧的顶部,就是这个栈帧的底部



新的栈帧开始了,由下图中间的一根长长的横线隔开俩个栈帧

sub esp,8; int  a,b这里声明了俩个int ,所以esp减小8个字节来为a,b分配空间

mov dword ptr[esp+4],[ebp+12]; a = m

mov dword ptr[esp],[ebp+8];b=n

这样,栈的情况变为:



ret8;返回,8是自动变量占用的字节数,当返回后,esp-8,释放参数,m,n的空间

 由此可见,通过ebp,能够很容易定位到上面的参数。当从func函数返回时,首先esp移动到栈帧底部(即释放自动变量),然后把上一个函数的栈帧底部指针弹出到ebp,再弹出返回地址到cs:ip上,esp继续移动划过参数,这样,ebp,esp就回到了调用函数前的状态,即现在恢复了原来的main的栈帧。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值