mirror of
https://github.com/gnu4cn/rust-lang-zh_CN.git
synced 2025-03-14 03:10:44 +08:00
Refining Ch04.
This commit is contained in:
parent
632b92bf61
commit
f6694b1b61
@ -14,23 +14,26 @@
|
||||
>
|
||||
> **The Stack and the Heap**
|
||||
>
|
||||
> 许多编程语言,都不要求进程考虑内存栈与堆。不过在像是 Rust 这样的系统编程语言中,某个值是在栈上还是在堆上,就会对语言的行为方式,造成影响,还会影响到不得不做出一些明确决定的理由。本章稍后将讲到的所有权的那些部分,是与内存栈和堆有关的,因此这里是关于他们的一点简要说明,作为预备知识。
|
||||
> 许多编程语言,都不要求咱们经常考虑堆栈和堆。但是,在 Rust 这样的系统编程语言中,某个值是在栈上,还是在堆上,会影响这门语言的行为方式,以及咱们必须做出某些决定的原因。本章稍后所有权的一些部分,就会与栈和堆结合讲解,因此在此要预先简要说明。
|
||||
>
|
||||
> 内存栈和堆,都属于在运行时代码可用内存的组成部分,但他们是以不同方式架构组织起来的。栈,the stack 以其收到值的顺序,保存着一些值,并以相反的顺序,将这些值移除。这被成为 *后进先出,last in, first out*。设想有一叠盘子:在添加更多盘子时,就要把新的盘子放在盘子堆顶上,而在要用个盘子时,就要从顶上拿。从底下或中间添加或拿走盘子,都是不行的!添加数据被称为 “压入栈,pushing onto the stack”,而移除数据被称为 *弹出栈,popping off the stack*。保存在栈上的数据,必须要有已知的、固定的大小。相反,那些运行时未知大小,或大小可能会变化的数据,就必须保存在堆上。
|
||||
> 栈和堆都是内存的部分,供代码在运行时使用,但他们的结构方式不同。栈是以其获取到值的顺序存储值,并按照相反的顺序移除值。这就是所谓的 *后进先出,last in, first out*。请设想一摞盘子:当咱们添加更多盘子时,就会把他们放到这堆盘子的顶端;当咱们需要某个盘子时,就从顶端取下一个。从中间或底部添加或移除盘子的效果并不好!添加数据被称为 *推入堆栈,pushing onto the stack*,移除数据被称为 *弹出栈,popping off the stack*。栈中存储的所有数据,必须有已知、固定的大小。编译时大小未知,或大小可能改变的数据,必须存储在堆上。
|
||||
>
|
||||
> 内存堆的组织程度较低:在将数据放在堆上时,就要请求确切数量的空间。内存分配器会在堆上找到一处足够大的空白位点,将其标记为正在使用中,然后返回一个 *指针,pointer*,即那个点位的地址。此过程被称为 *堆上内存分配,allocating on the heap*,而有时会去掉“堆”,而简称为 *内存分配,allocating* (而将值压入到栈上,则不被视为内存分配)。由于到堆的指针是已知的、固定大小的,因此就可以将该指针存储在栈上,而在想要具体数据时,就必须依循该指针。请设想正坐在某个餐馆里。在进到餐馆时,就要报出跟你们组的人数,进而餐馆员工就会找出一张可以坐下所有人的空桌子,并把你们带过去。在你们组有人迟到时,他们就可以询问是坐在哪张桌子,而找到你们。
|
||||
> 堆的组织程度则较低:在咱们把数据放在堆上时,咱们要请求一定数量的空间。内存分配器会在堆中,找到足够大的空位,将其标记为在用,并返回一个 *指针,pointer*,即那个位置的地址。这个过程称为 *在堆上分配,allocating on the heap*,有时也简称为 *分配,allocating*(将值推入栈,则不被视为分配)。由于到堆的指针,属于已知、固定的大小,因此咱们可以将该指针,存储在栈上,而当咱们需要具体数据时,就必须跟随这个指针。请设想在餐厅等待安排座位的情景。当咱们进入某家餐厅时,咱们要说明咱们团体的人数,然后接待员会找到一张适合每个人的空桌,并把咱们领到那里。如果咱们团队中有人来晚了,他们可以询问,咱们的座位在哪里,然后找到咱们。
|
||||
>
|
||||
> 由于在把数据压到栈上时,内存分配器绝不必搜寻一个位置来存储新数据,因此相比在堆上分配空间,把数据压入栈是要快得多的;存储新数据的地方,始终是在栈顶部。与此相比,在内存堆上分配空间则需要更多工作,由于内存分配器必须先找到一块足够大空间来保存该数据,并随后还要为准备好下一次内存分配,而完成对此次分配的登记。
|
||||
> 压入栈要比在堆上分配空间更快,因为分配器无需寻找存储新数据的位置;该位置总是在栈的顶部。相比之下,在堆上分配空间,则需要更多的工作,因为分配器必须首先找到一个足够大的空间来存放数据,然后进行簿记,为下一次分配做好准备。
|
||||
>
|
||||
> 因为必须要循着某个指针去获取到数据,因此访问内存堆上的数据,与访问栈上的数据相比,也要慢一些。当较少地在内存中跳跃时,现代处理器会更快。延续上面的比喻,设想餐馆里的一名服务员,正在接收来自许多台餐桌的点餐。那么一次获取到一个桌子的全部点餐,再去往下一桌,无疑是最高效的。而从餐桌 A 拿到一份点餐,再从餐桌 B 拿到一份点餐,随后又从餐桌 A 拿到一份,然后又从餐桌 B 再拿到一份,这样无疑就是慢得多的过程了。经由同一令牌,如果处理器处理的数据与另一数据靠近(就像在栈上那样),而不是远离另一数据(就像在内存堆上可能的情形),那么处理器无疑会更好地完成他的工作。
|
||||
> 访问堆中的数据,比访问栈中的数据要慢,因为咱们必须跟随指针才能到达那里。如果减少在内存中的跳转,那么现代处理器的速度就会更快。继续类比,请设想一下某个餐厅的服务员,从许多桌子上点菜的情况。最有效的方法是先处理一张桌子上的所有点餐,然后再处理下一张桌子上的点餐。从 A 桌点菜,然后从 B 桌点菜,然后再从 A 桌点菜,然后再从 B 桌点菜,这个过程就会慢得多。同样,如果处理器处理的数据,与其他数据距离较近(如栈中的数据),而不是较远(如堆中的数据),那么处理器就能更好地完成工作。
|
||||
>
|
||||
> 在代码对某个函数进行调用时,传入到该函数的值(潜在包含了指向内存堆上数据的指针),以及该函数的本地变量,都是被压入到栈上的。在该函数结束运行后,这些值就被从栈上弹出。
|
||||
> 当咱们的代码调用某个函数时,传入函数的值(可能包括指向堆上数据的指针)和函数的局部变量,会被推入栈。函数结束后,这些值会从栈中弹出。
|
||||
>
|
||||
> 对代码的哪些部分正在使用内存堆上的哪些数据进行追踪,最小化内存堆上的重复数据数量,以及对内存堆上的未使用数据进行清理而不至于耗尽内存空间等,都是所有权要解决的问题。一旦掌握了所有权,就再也不需要经常考虑栈和堆了,而清楚了所有权主要目的,是为着对内存堆进行管理,则会有助于解释所有权,为何会以他自己的方式运作。
|
||||
> 跟踪代码的哪些部分,正在使用堆上的哪些数据、尽量减少堆上的重复数据量,以及清理堆上未使用数据以免空间耗尽,这些都是所有权要解决的问题。一旦咱们掌握了所有权,咱们就不需要经常考虑栈和堆了,而清楚所有权的主要目的,是为管理堆数据这一点,有助于解释为什么他以这种方式工作。
|
||||
|
||||
|
||||
## 所有权规则
|
||||
|
||||
**Ownership Rules**
|
||||
|
||||
|
||||
首先,来看看这些所有权规则。在完成后面用于演示这些规则的示例时,请牢记这些规则:
|
||||
|
||||
- Rust 中的每个值,都有一个名为 *所有者,owner* 的变量;
|
||||
|
Loading…
Reference in New Issue
Block a user