堆(heap)和栈(stack)的理解和区别)
数据结构中的堆和栈
在数据结构中,栈是一种实现先进后出
的存储结构。
假设给定栈 S = (a0,a1,…., an-1),则称a0 为栈底,an-1 为栈顶,进栈顺序按照a0,a1 … ,an-1 进栈;出栈则依据“先进后出”的规则进行,an-1 先出栈,依次再到a0。
在实际编程中,可以通过两种方式实现栈:
-
数组,称为
静态栈
。 -
链表,称为
动态栈
。
而在数据结构中,堆则是通过排序的树形数据结构
,常用来实现优先队列等。假设有一个集合 K={k0,k1,…,kn-1},把它的所有元素按完全二叉树
的顺序存放在一个数组中,并且满足:
则称这个集合 K 为最小堆(或者最大堆)。
由此可见,堆是一种特殊的完全二叉树。其中,节点是从左到右填满的,并且最后一层的树叶都在最左边(即如果一个节点没有左儿子,那么它一定没有右儿子);每个节点的值都小于(或者都大于)其子节点的值。
内存分配的堆和栈
C、C++中内存分配如下三种形式:
-
静态存储区域分配
:它由编译器自动分配和释放,即内存在程序编译时就已分配,这块内存在程序运行期间一直存在,知道程序程序运行结束时才释放,如全局变量
与static变量
。 -
在栈上分配
:它同样也是由编译器自动释放和分配的,即在执行函数时,函数的局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元将被自动释放。需要注意的是栈内存分配运算置于处理器的指令集中,运行效率高,但分配的内存容量有限。 -
在堆上分配
:即动态内存分配
,它是由程序员手动完成申请和释放的。即程序在运行的时候由程序员使用内存分配函数(如malloc
)来申请多少内存,使用完后再由程序员使用释放函数进行内存释放。动态内存的生存期是由程序员自己决定的。需要注意的是,在堆分配了内存空间,就必须及时释放它,以免造成内存泄露。
C、C++中程序编译时分为5大存储区:
栈区
:由编译器在需要的时候分配,在不需要的时候自动清除变量存储区。里面的变量通常时局部变量和参数。堆区
:就是程序员通过使用函数new、alloc申请分配的内存块,它的释放由程序员自己控制。如果程序结束后还没释放掉,操作系统会自动回收。全局/静态区
:全局变量和静态变量的存储是放在一起的。已初始化的全局变量和静态变量放在一块区域,未初始化的全局变量和静态变量放在相邻的一块区域,在程序编译时分配。常量区
:存放常量字符串。程序代码区
:存放函数体(类的成员函数、全局函数)的二进制代码。
内存分配的堆与栈的区别
分配与释放方式
栈内存分配是由编译器自动分配与释放的。有两种方式:静态分配与动态分配
- 静态分配,是由编译器自动完成的,例如局部变量的分配,同时其生存周期是函数运行的过程中开始到结束自动释放,并不可以再次访问。
- 动态分配由alloca 函数分配,但栈的动态分配与堆不同,它的动态分配是由编译器进行释放。
堆内存分配则是完全由程序员手动申请与释放的。程序运行中由程序员使用内存分配函数来申请任意多少内存,使用完在由程序员使用内存释放函数释放内存。
分配效率问题
堆的分配效率要比栈低的多。
栈是机器系统提供的数据结构,计算机会在底层对对栈提供支持,例如,分配专门的寄存器存放栈的地址,压栈出栈都有专门的执行指令,这就决定了栈的效率比较高。一般而言,只要栈的剩余空间大于所申请空间,系统就将为程序提供内存,否则将报异常提示栈溢出。
而堆不同,是由C、C++函数库提供的,机制也相对复杂。例如,为了分配一块堆内存,首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆节点,然后将该节点从空闲节点链表中删除,并将该节点的空间分配给程序。而对于大多数系统,会在这块内存空间的首地址处记录本次分配的大小,这样,代码中的 delete 语句才能正确释放本内存空间。另外,由于找到的堆节点的大小不一定正好等于申请的大小,系统会自动将多余的那部分重新放入空闲链表中。很显然,堆的分配效率比栈要低得多。
分配的碎片问题
对堆来说,频繁分配和释放(malloc / free)不同大小的堆空间势必会造成内存空间的不连续,从而造成大量碎片,导致程序效率降低;
而对栈来讲,则不会存在这个问题。
申请大小限制
由于操作系统是通过链表来存储空闲的内存地址(内存是不连续的)的,链表的遍历方向是由低地址向高地址执行的。因此,堆内存的申请大小受限于计算机系统中有效的虚拟内存大小。
而栈不同,它是一块连续的内存区域,其地址是向下增长的,向内存地址减小的方向增长。由此可见,栈顶的地址和栈的最大容量一般都是由系统预先规定好的,如果申请的空间大于栈的剩余空间时,会报溢出错误。由此可见,相对于堆,能够从栈中获得的空间较小。
存储的内容
对栈而言,一般用于存放函数的参数
和局部变量
。
对堆而言,具体存储的内容是根据程序员需要。