堆与栈
数据结构的堆和栈
堆和栈都是一种数据项按序排列的数据结构。
栈是一个桶
栈就是一个装数据桶,是因为它是一种后进先出性质的数据结构。也就是后放入栈的数据,要先取出。
这个过程相当于我们要取出桶最底的东西,我们就需要先取出上层东西一样。
堆是一个倒置的树
堆是一种经过排序的树形数据结构,每个结点都有一个值。
因为堆的这个结构,所以堆的存取是随意的。虽然数据的存放是有顺序的,但是我们不需要按照顺序去存取。这好比如图书馆,每本书都有规定的位置,但是我们可以任意取走一本书,需要把目标书前面的书先取走。
内存分配中的堆和栈
程序是存放在ROM中,运行就需要加载到RAM中,RAM中会分区存储不同的信息。如图所示:
经典例子:
int a = 0; //全局初始化区
char *p1; //全局未初始化区
main()
{
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //123456\0在常量区,p3在栈上。
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10); //堆
p2 = (char *)malloc(20); //堆
百度百科中对内存中的堆栈介绍:
栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。
堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
增长方式
栈:由高地址向低地址增长。是一块连续的内存区域。
堆:由低地址向高地址增长。是不连续的内存区域。
空间分配(申请回收方式)
栈:
由操作系统自动分配释放。
例如:我们定义一个 char a; 系统会自动在栈上为其开辟空间。
堆:
一般由程序员分配、释放,若程序员不释放,程序结束时可能会由OS回收。不能正确释放,会造成内存泄露。
例如我们需要申请一个堆空间: malloc(10); 这样我们就开辟了10字节大小的堆空间。
程序、函数结束后,栈空间就会被系统收回。而堆空间在程序结束后,没有手动释放或者系统也没有释放,就会造成内存泄露,也就是程序结束了,但是产生的数据依然留在堆中。
申请限制
申请后系统的响应
栈:只要内存中栈的剩余空间大于申请空间,系统给程序提供内存空间做栈,否则就报异常提醒栈溢出
堆:操作系统中有一个记录空闲内存地址的链表(如下图),当系统受到程序堆空间的申请,会遍历链表,寻找(第一个)空间大于所申请空间的堆节点,然后将该结点从空间结点链表中删除,并将该结点空间分配给程序。
大多数操作系统,会在内存空间中的首地址中记录本次分配内存的大小。保证delete语句能够正确地释放内存空间(堆)。
找到的堆节点大小不一定刚刚好等于申请的大小,系统会自动将多余的部分重新放入空闲链表中。
申请效率
栈:系统自动分配,速度快,程序员无法控制
堆:由程序员申请才分配,速度慢,容易产生内存碎片,但是可控使用方便
申请大小
栈:栈最大容量是系统预先规定好的。windows下是2m。栈能申请的空间较小
堆:堆的大小受限于内存中的空闲链表大小。堆能申请的空间比较大。
存储内容
栈:在函数调用中,第一个进栈的是主函数中函数调用后的下一条指令的地址(也就是返回地址),然后子函数的参数进栈,然后是子函数中的局部变量。本次函数调用结束后,局部变量先出栈,然后参数,最后栈顶指针指向最开始的地址,也就是返回地址,程序继续运行。
堆:在堆的头部用一个字节存放堆的大小。堆中的内容由程序员安排
存取效率
在栈上的数组比指针所指向的字符串快。
总结
栈(stack):
- 和堆一样存储在计算机 RAM 中。
- 在栈上创建变量的时候会扩展,并且会自动回收。
- 相比堆而言在栈上分配要快的多。
- 用数据结构中的栈实现。
- 存储局部数据,返回地址,用做参数传递。
- 当用栈过多时可导致栈溢出(无穷次(大量的)的递归调用,或者大量的内存分配)。
- 在栈上的数据可以直接访问(不是非要使用指针访问)。
- 如果你在编译之前精确的知道你需要分配数据的大小并且不是太大的时候,可以使用栈。
- 当你程序启动时决定栈的容量上限。
堆(heap):
- 和栈一样存储在计算机RAM。
- 在堆上的变量必须要手动释放,不存在作用域的问题。数据可用 delete, delete[] 或者 free 来释放。
- 相比在栈上分配内存要慢。
- 通过程序按需分配。
- 大量的分配和释放可造成内存碎片。
- 在 C++ 中,在堆上创建数的据使用指针访问,用 new 或者 malloc 分配内存。
- 如果申请的缓冲区过大的话,可能申请失败。
- 在运行期间你不知道会需要多大的数据或者你需要分配大量的内存的时候,建议你使用堆。
- 可能造成内存泄露。