内存中堆和栈,到底有什么区别?

科技   2024-12-13 12:01   北京  

一、什么是内存?

内存是计算机的重要组成部分,是主储存器中的一种,由于计算机中所有程序的运行都是在内存中进行的,内存又被称为主存,其作用是暂存CPU中的运算数据,以及与硬盘等外部存储设备交换的数据。所以它是程序与CPU进行沟通的桥梁

相较于大容量存储(比如硬盘),内存速度快,但每比特造价成本高,位置一般更靠近CPU,还常被用于硬盘缓存写入缓冲区,以提高读写性能

尝试用鼠标在绘图工具上,画了一副计算机相关的图

二、内存分配中的栈和堆

我们都知道计算机中的程序都是与内存打交道的,数据又都是0和1这种2进制形式存放在内存中,为了能够有序地管理与控制数据,现代计算机都是将内存划分为栈区Stack堆区Heap,二进制代码的存放区,还有静态数据的存放区等

我们来看一下上面这张图,其中:

  1. 栈区stack,由操作系统自动分配释放,存放函数的参数值,局部变量的值等
  2. 堆区heap,一大块自由内存区,由程序员手动申请,比如执行malloc函数申请的区域;也要手动释放
  3. 读写段,也叫全局区,这里包含BSS段、数据段(data)、代码段(文字段)等。其中BSS存放的是未初始化的全局变量和静态变量,数据段存放的是初始化后的全局变量和静态变量
  4. 只读区,也叫程序代码区,存放二进制代码
  5. 上面这些区域都是用户空间,内核虚拟内存则属于内核空间,给内核程序使用

如果是编写C、C++、Rust这种系统级编程语言的程序员们,那么会经常和内存中的栈与堆打交道。这里的堆Heap和栈Stack(另外许多中文资料会把它翻译成"堆栈",其实指栈),并不是数据结构中的堆和栈,而是内存划分的一种形式;二者原理类似,但应用场景不同,所以不能混淆

栈stack,在数据结构中,它的特点是先进后出LIFO,它只能在栈顶进行操作,比如

  1. 压栈Push:将一个元素从栈顶压入栈中
  2. 弹栈Pop:弹出栈顶元素并返回

大家可以想象成,栈类似于枪械的弹夹,同样是只能一端操作,先进后出

堆heap, 在数据结构中是一种经过排序的非连续形状树形数据结构,特指"完全二叉树",存取随意,每个结点都有一个值。常用来实现优先队列,其特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆

我们编写的程序一般不会直接对内存中的地址,存取数据,因为这样会诱发空间无序导致的浪费,或者可能会使用到其他程序已经占用的内存空间。所以我们编写的程序一般都是通过操作系统OS,来请求内存,而不是直接使用内存

操作系统是一种特殊的软件,是沟通硬件和软件的"桥梁"。它会对内存进行管理。程序向OS申请内存,OS会自动的去寻找可用内存,并给程序使用。所以我们接下来讲解内存中的栈和堆,会结合操作系统的视角来看

栈区

栈区:一块地址连续的空间。容量小但存取数据的效率高,由操作系统自动分配释放 ,一般存放函数的参数值,局部变量的值,和其他函数相关信息等。其操作方式类似于数据结构中的

其中:

  1. ESP:栈指针寄存器,其内部存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶(x86体系下)
  2. EBP:基址指针寄存器,其内部存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部
  3. 栈中的单元,被称为栈帧frame;先进后出

栈的特点:

  1. 栈是程序运行前,操作系统就已经分配好的空间,因此运行时分配几乎不需要时间,由操作系统自动的申请和销毁空间(压入和弹出);
  2. 另外栈内存空间的地址是连续的,栈上内存的分配和回收,只需移动栈顶指针就能实现;另外栈上一般存放固定尺寸的值
  3. 栈的容量非常有限(比如如今的Linux默认堆栈大小为8MB,当然我们也可以临时修改),所以栈不适合存放太大的值
  4. 栈的生长方向是向低内存方向生长,如果栈没有限制的话,会影响到内存的其他区域,后果非常严重
  5. 如果栈的空间不足,还将元素压入栈的话,就会发生栈溢出,即Stack Overflow这个很熟悉吧,全球最大的技术问答网站,我们程序员经常阅读的

堆区

堆区:是内存中一大块自由的空间,存取数据的效率较低,一般由程序员手动申请和释放和数据结构中的堆heap毫无关系,倒是用类似于"链表"的方式来管理内存

在C语言中,通过调用如malloc等函数来申请内存,当系统收到程序的申请时,会遍历一个记录空闲内存地址的链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。使用完毕后需要调用比如free函数,来显式释放,以避免内存泄漏

不同于栈的空间有限,堆是很大的自由存储区,分配内存很灵活。比如像数组这种,尺寸不固定的或者尺寸较大的数据,一般会放入堆中;但频繁地申请、释放堆内存,会造成内存空间的不连续,也就是内存碎片,使程序效率降低;而栈不存在这种问题,可靠且高效

三、栈的效率为什么比堆高?

栈的效率为什么比堆高?栈和堆本质上都是内存的一块区域,那为啥都说栈就比堆效率快呢?

  1. 栈是程序运行前就已经分配好的空间,所以运行时分配几乎不需要时间(如今栈也可以动态分配,由编译器进行优化);而堆是程序运行时动态申请的,申请前还得寻找足够大小的空间,这是因为堆内存多次分配释放后会造的内存碎片
  2. 栈的物理地址空间是连续的,而堆不是,内存连续会有更大的可能(空间局部性),被完整地缓存到CPU中的Cache中,尽可能利用CPU缓存Cache,从而提高程序的性能;感兴趣地可以去阅读如何利用缓存,让CPU更有效率地执行代码?

另外我们平时编写程序时,也应该尽可能让我们程序,所需要的数据紧凑,内存对齐,从而充分利用CPU缓存

  1. 在CPU中,栈有专门的寄存器,来操作栈,比如x86中的esp、ebp;而堆需要由操作系统去调度;另外很多CPU对压栈和出栈操作有硬件(指令)上的支持,效率很高;如果内存不够,可能还要触发swap让内存和磁盘进行交互等一系列复杂操作
  2. CPU访问栈中的数据时,可以通过指针直接访问;而访问堆中的数据,得分2次,第一次得取得指针,第二次才是真正得数据


本文到这里就结束啦,主要是讲解内存中堆、栈的特点和区别,可不能和数据结构中的堆栈混淆。后面会进一步讲解栈是如何与函数调用相结合的?我们下期再见~

点赞在看收藏就是对笔者最好的催更!

参考资料:
Stack-based memory allocation

堆和栈访问效率哪个更高_堆和栈 哪个访问效率高


END

来源:小牛呼噜噜


版权归原作者所有,如有侵权,请联系删除


推荐阅读

一个面向对象的C语言框架!

FreeRTOS 单核、多核的调度策略

一个高效、安全、可靠的串口通讯开源方案


→点关注,不迷路←

嵌入式微处理器
关注嵌入式相关技术和资讯,你想知道的都在这里。
 最新文章