栈(Stack)、栈溢出问题

在Linux、Windows下C语言内存布局(内存模型)中曾经说到程序的虚拟地址空间分为多个区域。栈(Stack)可以存放函数参数、局部变量、局部数组等作用范围在函数内部的数据,他的用途就是完成函数的调用。

栈内存由系统自动分配和释放:发生函数调用时就为函数运行时用到的数据分配内存,函数调用结束后就将之前分配的内存全部销毁。所以局部变量、参数只在当前函数中有效,不能传递到函数外部。

栈的概念

在计算机中,栈可以理解为一个特殊的容器,用户可以将数据依次放入栈中,然后再将数据按照相反的顺序从栈中取出。也就是说,先放入的数据最后才能取出,而最后放入的数据须先取出。这被称为先进后出(First In Last Out)原则。

放入数据常称为入栈或压栈(Push),取出数据常称为出栈或弹出(Pop)。如下图:

access 溢出栈空间 栈溢出后果_内存结构

可以发现,栈底始终不动,出栈入栈只是在移动堆栈,当栈中没有数据时,栈顶和栈底重合。

从本质上来讲,栈是一段连续的内存,需要同时记录栈底和栈顶,才能对当前的栈进行定位。在现代计算机中,通常使用ebp寄存器指向栈底,而使用esp寄存器指向栈顶。随着数据的进栈出栈,esp的值会不断变化,进栈时esp的值减小,出栈时esp的值增大。

ebp和esp都是CPU中的寄存器;ebp是Extend Base Pointer的缩写,通常用来指向栈底;esp是Extend Stack Pointer的缩写,通常用来指向栈顶

如下是一个栈的实例:

access 溢出栈空间 栈溢出后果_栈内存_02

栈的大小以及栈溢出

对每个程序来说,栈能使用的内存都是有限的,一般是1M~8M,这在编译时就已经决定了,程序运行期间不能再改变。如果程序使用的栈内存超出最大值,就会发生font color=#1E90FF>栈溢出(Stack Overflow)错误。

一个程序可以包含多个线程,每个线程都有自己的栈。严格来说,栈的最大值是针对线程来说的,而不是针对程序。

栈内存的大小和编译器有关,编译器会为栈内存指定一个最大值,在VC/VS下,默认是1M,在C-Free下,默认是2M,在Linux GCC下,默认是8M。

当然,我们也可以通过参数来修改栈内存的大小。以VS2010为例,在工程名处右击,会弹出一个菜单,选择“属性”,会出现一个对话框,如下图所示:

access 溢出栈空间 栈溢出后果_内存结构_03

该图中,我们将栈内存设置为4M。提示:栈也经常被称为堆栈,而堆依然被称为堆,所以堆栈这个概念并不包含堆,注意区分。

当程序使用的栈内存大于默认值(或修改后的值)时,就会发生栈溢出(Stack Overflow)错误。使用VS2010并切换到Debug模式,并运行以下代码:

int main(){
    char str[1024*1024*2] = {0};
    return 0;
}

局部字符数组str存储在栈上,占用2M的内存,超出了默认值1M,所以会发生栈溢出错误,如图:

access 溢出栈空间 栈溢出后果_access 溢出栈空间_04