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

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

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

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

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

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

对于一个栈来说,寄存器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函数前,栈的情况是下面这个样子:

Java 栈 栈帧 默认大小 栈帧内容_内核

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

push m

push n;俩个参数压入栈

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

Java 栈 栈帧 默认大小 栈帧内容_Java 栈 栈帧 默认大小_02

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

_func:

push ebp;

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

Java 栈 栈帧 默认大小 栈帧内容_内核_03

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

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

这样,栈的情况变为:

Java 栈 栈帧 默认大小 栈帧内容_main函数_04

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

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