1. 为什么内存要区分堆与栈
introduction
大家都是内存条, 为什么要区分堆区与栈区. 我之前在go语言的时候提过这些概念: Goroutine的默认栈大小是2K, 栈扩张, 以及Go语言里一部分变量可能会逃逸, 即栈区变堆区, 那么为什么要把同样的内存条划出两个区呢?
栈
当时在汇编语言里学过, 如果把地址加一, 就能获得一个地址的新内存, 用来存放变量, 同样的我们pop一下就能释放一个地址. 这个道理在Go语言里面使用, 我们通过这种方式使用着2K大小的G栈.
这种做法毫无疑问是最快的, esp(栈指针)上移/下移一位就能获得新的内存. 已经是硬件级别的速度了, 如果我们想要在函数里分配局部变量, 就这么做是最快的, 最后在函数结束的时候统一把ESP刷到顶就算回收完
既然栈这么快, 那我们只搞栈就好咯? 栈的问题是什么: 栈必须操作连续的内存, 你不能搞出带一个一个窟窿的内存, 在我们释放内存的时候你不能说给我把我之前的内存释放了, 但我留下. 你要释放, 就必须从tail开始释放. 这种特性导致我们只会在函数里, 分配局部变量的时候会用它, 因为函数是短暂的, 而且函数的清理比较"一体化" , 不会出现需要在中间挖窟窿的需求, 如果函数结束, 我们直接把函数所有用到的栈区从tail到head全部刷空就好了.
堆
有了函数局部变量这种, 能一次性全清理的情况, 自然就会有不能一次性全清空的情况, 设想我们有10个全局变量, 某个情况下我们可能会需要释放其中某一个, 这种情况就是我们上面说的挖窟窿, 其他不动, 删掉中间某一个. 而且发生时间非常不固定, 指不定某个时间下就要求清空其中某一个, 因此通过pop的那种一把刷清的情况就不存在. 这种情况下, 就出现了我们以前学过的malloc, 各种动态内存管理的机制.
我理解栈一定是最快的, 分配快, 释放快, 但pop操作要求连续, 因此不得已我们整出了堆的概念, 在C语言里堆内存要求手动的, 指定地址的, 去释放, 不停的在内存里挖窟窿非常容易出现内存零碎化的情况, 并且也容易产生内存忘记释放(内存泄露)的问题
Last updated
Was this helpful?