一、栈
栈作为一种特殊的数据结构而存在(“后入先出”存储),是一种只能在一端进行插入和删除操作的特殊线性表。
大多数CPU上的程序实现使用栈来支持函数调用操作。栈用来传递函数参数、存储返回信息、临时保存寄存器原有值以用于回复以及存储局部数据。
栈有很多自己的特性,它具有记忆功能,对栈的插入与删除操作中,不需要改变栈底指针;而且栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。因此栈作用就是用来保持栈帧的活动记录(即函数调用)。
栈相对整个系统而言,调用栈相对某个进程而言,栈帧则是相对某个函数而言,调用栈就是正在使用的栈空间,由多个嵌套调用函数所使用的栈帧组成。
如果C语言的地址符'&'被应用到一个局部变量时,栈则需要为该变量生成一个地址值即变量的地址值分配一个空间。
二、栈帧
栈帧表示程序的函数调用记录,而栈帧又是记录在栈上面,很明显栈上保持了N个栈帧的实体,那就可以说栈帧将栈分割成了N个记录块,但是这些记录块大小不是固定的,因为栈帧不仅保存诸如:函数入参、出参、返回地址和上一个栈帧的栈底指针等信息,还保存了函数内部的自动变量(甚至可以是动态分配内存,alloca函数就可以实现,但在某些系统中不行),因此,不是所有的栈帧的大小都相同。
单个函数调用操作所使用的栈部分称为栈帧结果。栈帧结构的两端由两个指针来指定。寄存器ebp通常用作栈帧的指针、esp用作栈的指针。esp随着数据的入栈和出栈。因此对于函数中大部分数据的访问都是通过基于帧帧指针ebp来实现。
ebp指向当前位于系统栈最上边一个栈帧的底部,而不是系统栈的底部; esp所指的栈帧顶部和系统栈的顶部是同一个位置。
例:
程序代码:
#include<stdio.h> #include<stdlib.h> int stack_test(int a,int b) { printf("before write: 0x%x\n",b); int *p=&a; p++; *p=0xdddd; printf("after write: 0x%x\n",b); int c=0xcccc; return c; } int main() { int a=0xaaaa; int b=0xbbbb; int ret=stack_test(a,b); printf("You should run here\n"); return 0; }
运行结果:
结果分析:
程序运行中数据的改变:
程序运行前,&b=0xbbbb;
当程序运行后,系统重新分配一个临时空间。*p=&a,即此时临时拷贝a至新的内存空间;p++,*p向上移动一位,而此时*p的地址被赋予0xdddd.
而栈以“后入先出”存储,即此时*p是该新栈帧的底部,先一步出去,因而&b=*p=0xdddd.
例:
程序如下;
#include<stdio.h> #include<stdlib.h> int bug() { system("reboot"); exit(0); } int stack_test(int a,int b) { int *p=&a; p--; *p=bug(); int c=0xcccc; return c; } int main() { int a=0xaaaa; int b=0xbbbb; int ret=stack_test(a,b); printf("You should run here\n"); return 0; }
运行结果:
系统重启
结果分析:
情况和上例类似,当程序运行到bug()时,程序内部函数system("reboot")会命令系统重启,而随后exit(0)以正常情况结束程序。