一般一个应用程序在内存中的存放会分多个数据区域:
├———————┤低端内存区域
│……│
├———————┤
│动态数据区│
├———————┤
│……│
├———————┤
│代码区│
├———————┤
│静态数据区│
├———————┤
│……│
├———————┤高端内存区域
而其中的动态数据区就是堆和栈的存放区域。虽然堆和栈都归于动态数据存储区域,但是他们本身还是有区别的。
1. 内存分配方式不同
栈:由编译器自动分配的内存,不需要程序自己分配。但是栈本身大小是有限的。
堆:由程序运行时动态分配,同时要注意手动回收。其大小视虚拟内存大小而定。
2.内存的存储地址生长方向不同
栈:在内存中由高地址向低地址延生。
堆:在内存中由低地址向高地址延生。
3.数据的存储不一样
栈:其数据在内存中是线性连续的。
堆:其数据存储是不连续的,可以认为是链表的形式分配和存储。
总体而言:栈的使用有限制,我们只能申请,不能管理。而堆相对就自由很多,可以随时申请和管理内存数据。但是堆的过多使用会自然引入内存碎片,因为其存储是不连续的,同时不及时回收会导致内存泄露和内存不足。另一方面,由于堆内存的申请要耗费更多的时间,因此效率其实是不高的。在实际使用中需要尽量少用堆的申请。
栈的重要作用:栈一般用来存储临时变量,也就是函数中的局部变量。另外的一个重要作用就是存储代码运行的内存地址,也就是可以保存一个函数运行的现场。
假设调用函数为:
int func(int para1, int para2)
{
int var1;
int var2;
var1 = para2;
var2 = para1;
return 0;
}
则调用这个函数引起的栈变化如下:
├———————┤高端内存区域 (ebp栈底,保存现场)
|-------para2--------| 最右边的参数
|---------para1------| 一次向左移动,压入参数
|--------ret-----------| 函数调用返回的地址(esp),之后开始跳到func运行
|----------------------| 继续将此函数的ebp压栈,保存现场,并用esp代替(至少unix是这样)
|---------var2--------| 内部变量
|----------var1-------| 内部变量
|....执行代码.........|
|-----------------------| 低端内存区域(esp栈顶)
我们可以看到首先会压入函数传入的参数,并且是从最右边开始压入,依次向左。然后压入这个函数的返回地址,接着开始跳转到该函数执行。在执行代码之前,会再次将ebp压栈并用esp代替,保存现场。然后开始压入函数的临时变量,最后开始执行代码。在执行完之后会依次向上出栈(pop)。当回到最初的ebp栈基时再从eip中取得下一个执行指令开始往下执行。
从上述我们可以预见,如果一个函数递归调用本身而无法返回,则栈会无限延伸下去,直到栈溢出,这严重的话肯恩会导致系统崩溃。同时如果函数返回值被修改,则出栈的时候无法找到正确的返回地址,一样会导致问题。所以使用时候需要多加注意。