每个编程语言都有自己的内存管理模型,C++用的是堆栈模型。栈和堆本质上都是程序运行时能够访问的内存。

使用特性

栈在程序开始运行时,就已经被分配了固定的大小,这个大小可以通过编译选项或系统配置改变。所以栈可以理解为“运行时大小固定的连续内存”。栈就像“数组”一样,只要程序跑起来了,这块内存不管你用了多少,都属于你这个进程。所以栈空间如果设置得特别大,会让别的进程没内存可用。

堆也称为动态内存,在C++中new了变量或者malloc了一块内存,操作系统才会给你这个进程新加一块内存。所以堆的内存是随用随申请的。既然是随用随申请,堆的内存就未必是连续的,与用数组理解栈相对,堆可以用“链表”来近似理解,只不过这个“链表”是支持随机访问的。

因此何时使用栈/何时使用堆,就很明显了:当程序在生命周期中占用内存稳定可预期时,只用栈即可满足要求;反之则用堆栈的组合。

效率特性

接上节,既然栈有“数组”的连续、大小固定的特性,那我们在使用栈内存的时候就可以做一些优化,比如通过后进先出(LIFO)的规则管理栈,这也是栈被称为栈的原因。

举个简单的例子
(1)程序进入main函数后,栈上开了个空间用来存储main函数的局部变量;
(2)main函数中调用了foo函数,此时程序进入foo函数,栈上又开了个空间存储foo函数中的局部变量;
(3)随后foo函数执行完毕,程序退出foo函数并跳回main函数,此时foo函数的局部变量就没用了,栈要释放这部分空间。

如果使用LIFO的思路来管理,栈上内存的分配过程就是:main -> main|foo -> main。
同我们平时使用指针操作内存一样,程序的内存访问也使用指针,暂且称之为p。当main函数开始执行时,p指向main空间的首地址;当进入foo后,p指向foo空间的首地址;当foo执行完毕后,p又指回main空间的首地址。因此用管理连续内存,可以直接通过p++/p--来控制增加或减少目前已经使用的栈空间。同时这部分空间已经得到预分配,完全由进程自己控制,所以这个过程里没有系统调用。

堆未必是连续的,因此管理堆区内存实际上要维护一张表。表上记录了当前进程申请开辟了哪些内存块,这些内存块的首地址。所以堆区内存的访问多了一步查表。同时堆区内存的申请与释放都需要进行系统调用。开销就大起来了。

所以一般来说,在效率上,栈 > 堆

使用特性

C++er都听说过不能返回局部变量,所以如果想返回局部变量,要把变量开在堆区,返回变量指针。

但同时也有一种做法,是在调用会产生局部变量的函数前,先在栈上声明变量,然后把变量的引用传进函数,用这个变量接收函数的结果。

所以使用特性更多的是一种习惯,并非决定使用栈或堆的决定性因素。

其他

栈空间的申请在程序运行之前进行,所以如果内存空间不够了,程序会跑不起来。

堆空间也有申请不出来的情况,但堆空间申请不出来的时候,程序往往都跑了很久了。

如果觉得我的文章对你有用,请随意赞赏