Appearance
在正式进入所有权之前,我们需要了解计算机的内存,变量存储在哪,以及不同存储位置的特性。内存是所有权的物理基础,而所有权的核心就是在解决一块内存何时会被释放, 一些值究竟该被 “移动” 还是 “拷贝”。
不过我们并不用太深入的去了解内存机制,我们的内存主要是三个部分,而且都非常好理解:
1. 栈 Stack
栈内存按照顺序存储值并以相反顺序取出值,也就是更常说的后进先出(LIFO)。重要的是:栈内存存储的内容,大小必须在编译时期已知,例如标量类型。
我们可以把栈比作一个开口封底的管道,最先向里面存入的东西,需要依次拿出它上面的东西才能到它,我们可以用三个词语来描述栈的特点:
- 有序、极速、有限
栈的优点
栈的优点是:
- 有序
- 极速
得益于栈的设计,栈是连续的、后进先出的,所以栈可以只存在两个操作:
- Push(压栈、入栈)
- Pop (出栈)
你要么 push 一个东西入栈到栈顶,要么 pop 最后一个东西出栈,所以它是有序的。
而且栈的速度也是非常快,所有操作几乎是瞬间发生的,在处理器中,专门有一个寄存器叫做栈指针(Stack Pointer),入栈和出栈只需要对这个栈指针进行加减法运算,所以速度非常的快。
所有程序都会有一个或更多个栈,每个栈都有一个栈指针
缺点
不过栈是有限的,为了防止滥用和非连续性,栈内存往往不能很大,不光是为了防止恶性 Bug,更大的问题其实是物理上的无法被突破。
例如,如果我们让栈很大,10GB,虽然感觉这么做非常好,而且现在很多电脑的配置,内存都 32、64GB 了,为了完成壮举,这 10GB 也不算什么,但是,理论总是很美好的不是吗?
为了满足栈的运行条件(连续性),我们必须为程序找出一段 5GB 大小的、没有中断的、连续的内存空间。 这不太可能,不光是因为操作系统往往有成千上百个进程在运行,每个进程都分走一点,虽然能有这么大的剩余空间,但是他们是否还是连续的,那就不好说了。
还有就是,如果真的给一个程序设置了 10GB 的栈内存,我们就得考虑一个问题,它真的用得着这么多吗?如果它只用了 10KB,但是我们硬是给它分了 10GB,也就是它几乎损耗了 99.99% 的内存,而且为了内存安全,系统不会允许其他线程涉足这块内存,即使它们是空的也不行...
所以现实是什么?那就是每个进程的站非常小,几乎只有几MB,不过这几MB其实已经满足了基础的运行。如果还需要读写更大的内容,没关系,我们可以用堆(Heap)
2. 堆 Heap
堆的特点其实和栈相反,它没有栈的速度、不像栈那样有序,它存储的位置是碎片化的,内存有空闲的地方就存在哪里,也正因为如此,访问速度很缓慢。不过它有一个最大的特点:足够大!
堆的访问需要依靠指针,一个堆数据被存储的时候,会往栈中压入一个指针指向这块堆上的内存,有需要的时候取出指针然后取寻找堆上的那块内存。
堆是一个缺乏组织的数据结构,有一个非常经典的例子:
想象一下去餐馆就座吃饭:进入餐馆,告知服务员有几个人,然后服务员找到一个够大的空桌子(堆上分配的内存空间)并领你们过去。如果有人来迟了,他们也可以通过桌号(栈上的指针)来找到你们坐在哪。
3. 静态内存 Static Memory
与栈和堆的爱恨情仇不一样,静态内存是三个内存中无冤无仇的那一个。
静态内存有几个特点:
- 大小必须在编译时期已知,主要用于存放全局内容,我们只需要只读的静态变量。
- 静态内存的生命周期是和程序共同存在的,也就是程序退出才会清理静态内存。
- 可写的静态变量是 unsafe 的。
总结
最后,我们来总结一下三个内存的特性:
- 栈:编译时期确定大小,有序、快速但很小。
- 堆:虽然速度慢且无序,但是足够大,而且现代内存对堆的优化还在进步。
- 静态内存:编译时期确定大小,访问效率高,与程序共存亡。