4.1.0 写在正文之前
在学习了Rust的通用编程概念后,就来到了整个Rust的重中之重------所有权,它跟其他语言都不太一样,很多初学者觉得学起来很难。这个章节就旨在让初学者能够完全掌握这个特性。
本章有三小节:
- 所有权:栈内存 vs. 堆内存(本文)
- 所有权规则、内存与分配
- 所有权与函数
喜欢的话记得点赞、收藏加关注哦,想要跟着学下去记得关注专栏哦
4.1.1. 什么是所有权
所有权是Rust最独特的特性,它让Rust无需GC(垃圾收集器)就可以保证内存安全。
所有程序在运行时都必须管理它们使用计算机内存的方式。有的语言依靠垃圾收集机制 ,在程序运行时,它们会不断寻找不在使用的内存(比如C#);在其他语言中,程序员必须显式地分配和释放内存(比如C/C++)。
Rust不同于前两种。Rust使用所有权系统 来管理内存,这个系统里还有一套规则,而编译器在编译时就会检查这套规则,而且这种做法不会产生任何的运行时开销。也就是说,在程序运行时,这种所有权特性不会减慢程序运行的速度,因为Rust把内存管理相关工作都提前到了编译时。
4.1.2. 栈内存(Stack) vs. 堆内存(Heap)
一般来说,程序员不会经常考虑栈内存与堆内存之间的区别。对于Rust这样的系统级编程语言来说,一个值它是在栈内存上还是在堆内存上对语言的行为和你要做的某些决定是由更大影响的。
在代码运行时,栈内存和堆内存都是可用的内存,但他们的结构很不相同。
4.1.3. 存储数据
1. 栈内存
栈内存按值的接收顺序来存储,按相反的顺序来将他们移除(后进先出,Last In First Out ,简写为LIFO)。
添加数据叫压入栈(压栈),移除数据叫弹出栈(出栈)。
所有存储在栈内存上的数据必须拥有已知的固定的大小。 相反的,编译时大小未知的数据或是运行时大小可能发生变化的数据必须存放在堆内存上。
2. 堆内存
堆内存的的内存组织性差一些。当把数据放入堆内存时,会请求一定的空间。操作系统会在堆内存中找到一块足够大的空间,把它标记为在用,并返回一个指针,也就是这个空间的地址。这个过程叫做在Heap上进行内存分配,有时简称为"分配"。
3. 指针与内存
因为指针是固定大小的,可以把指针放在栈内存上。但如果想要指针所指向的具体数据时,就必须得使用指针所指向的地址来访问它。、
把数据压到栈内存上比在堆内存上分配要快得多:
-
在栈内存上,操作系统不需要寻找用来存储新数据的空间,那个位置永远都在栈内存的顶端(栈内存的末尾位置,也就是当前可用的栈内存的起始位置)。
-
在堆内存上分配空间则需要做更多的工作:操作系统首先需要找到一个足够大的空间来存放数据,然后要做好记录方便下一次的分配。
4.1.4. 访问数据
访问栈内存中的数据要比访问堆内存中的数据快,因为需要通过指针才能找到堆内存中的数据,多了指针跳转这么一个环节,它属于间接的访问。而对于现代的处理器来说,由于缓存的缘故,如果指令在内存中跳转的数据越少,那么速度就越快。
如果数据存放的距离比较近,那么处理器的处理速度就会更快一些,例如放在栈内存上;反之,如果数据之间距离较远,那么处理速度就会慢一些,例如放在堆内存上(在堆内存上分配大量的空间也是需要时间的)。
4.1.5. 函数调用
当代码调用函数时,值被传入函数(也包括指向堆内存的指针)。函数本地的变量被压在栈内存上。当函数结束后,这些值会从栈内存上弹出。
4.1.6. 所有权存在的原因
所有权解决的问题:
- 跟踪代码中分配的堆内存空间,换句话说就是跟踪代码的哪些部分正在使用堆内存的哪些数据
- 最小化堆内存上的重复数据量
- 清理堆内存上未使用的数据以避免空间不足
一旦懂了所有权,就不用经常地去想堆内存和栈内存了。但是知道管理堆内存数据是所有权存在的原因有助于解释它为什么会这样工作。