首先引用百度百科的经典解释:“栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。
实际上,可以简单理解为:栈帧就是存储在用户栈上的(当然内核栈同样适用)每一次函数调用涉及的相关信息的记录单元。也许这样感觉更复杂了,好吧,让我们从栈开始来理解什么是栈帧...
栈帧表示程序的函数调用记录,而栈帧又是记录在栈上面,很明显栈上保持了N个栈帧的实体,那就可以说栈帧将栈分割成了N个记录块,但是这些记录块大小不是固定的,因为栈帧不仅保存诸如:函数入参、出参、返回地址和上一个栈帧的栈底指针等信息,还保存了函数内部的自动变量(甚至可以是动态分配内存,alloca函数就可以实现,但在某些系统中不行),因此,不是所有的栈帧的大小都相同。
二、实例
#include <stdio.h>
int Add(int a,int b)
{
int c = 0;
c = a+b;
return c;
}
int main()
{
int a = 3;
int b = 4;
int sum = 0;
sum = Add(a,b);
printf("%d\n",sum);
return 0;
}
以上是一个求和函数的例子,
接下来先了解一下栈帧的基本结构
当调试程序的时候,查看【调用堆栈】,如下图
我们发现,在main函数执行之前,tmainCRTStartup函数先调用,然后_tmainCRTStartup函数,然后才执行的main函数。
每次执行程序都会进行函数调用,我们把这个过程称为函数调用过程,这个过程主要为函数开辟栈空间,用于保存本函数调用时临时变量的保存,这块空间我们称为函数栈帧。
那么问题来了,我们如何维护栈帧呢?
接着,我们需要了解一下寄存器,在函数调用过程中ebp和esp两个寄存器存放了维护这个栈的栈顶和栈低的指针。
比如:分配栈空间时,ebp存放指向函数栈帧栈低的指针,esp存放了指向函数栈帧栈顶的指针。
当我们详细研究函数调用过程,我们还需要知道一点汇编代码。
1、从main函数的地方开始,对main函数调用就需要main函数创建栈帧,接下来,我们看看main函数的栈帧是如何创建的。
2、接下来就是sum函数调用过程
按F11我们执行call指令,再按F11我们跳转到sum函数执行代码处
最后,就是函数的返回部分
有人问了,我们为啥要学习栈帧呢?
我们看个案例
#include<stdio.h>
void fun()
{
int tmp = 10;
int *p = (int *)(*(&tmp+1));
*(p-1) = 20;
}
int main()
{
int a = 0;
fun();
printf("a=%d\n",a);
return 0;
}
不同的环境下结果也是不同,VC6.0下运行结果是
然而,在VS中是可以编译,但是运行程序会报错,直接崩溃。
在centos6.5上编译不过去的,这就是