编程网

Rust所有权和stack、heap

Rust所有权

所有权是Rust语言最独特的特性,该特性让Rust无需垃圾回收机制(garbage collector)就能保证内存安全,因此理解Rust所有权的原理非常重要。

什么是所有权呢?

Rust的核心特性就是所有权,我们知道所有程序在运行时都必须管理他们使用计算机内存的方式,比如说python C#都有垃圾回收机制,在程序运行时,垃圾回收机制会不断的寻找不再使用的内存,将这些内存进行释放;还有一些编程语言,开发人员必须显式的分配和释放内存,比如说C、C++。而Rust采用了第三种方式,就是通过所有权系统管理内存,所有权系统包含一组编译器在编译时检查的规则,编译器在编译时就会根据这一系列的规则进行检查,在运行式,所有权系统的任何功能都不会减慢程序,也就是说Rust将内存管理工作提前到了编译部分。

stack 和 heap

分别对应栈(stack) 和 堆(heap),在很多开发语言中程序员不需要经常考虑这两个概念,但是在像Rust这样的系统编程语言中,一个值是位于栈内存还是堆内存上对语言的行为和开发人员开发时做的某些决定是有非常大的影响的。

栈和堆都是代码在运行时可供使用的内存,但是它们的结构不同。栈以放入值的顺序存储值并以相反顺序取出值。这也被称作 后进先出(last in, first out)。举个例子,比如往箱子中放书,先放进去的在底部,后放进去的在顶部,如果想要往外拿,只能从顶部拿,不能从中间也不能从底部拿走或者增加书籍!增加数据叫做 进栈(pushing onto the stack),而移出数据叫做 出栈(popping off the stack)。

栈中的所有数据都必须占用已知且固定的大小。在编译时大小未知或大小可能变化的数据,要改为存储在堆上。堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 指针(pointer)。这个过程称作 在堆上分配内存(allocating on the heap),有时简称为 “分配”(allocating)。将数据推入栈中并不被认为是分配。因为指针的大小是已知并且固定的,你可以将指针存储在栈上,不过当需要实际数据时,必须访问指针。

想象一下去餐馆就座吃饭。当进入时,你说明有几个人,餐馆员工会找到一个够大的空桌子并领你们过去。如果有人来迟了,他们也可以通过询问来找到你们坐在哪。

入栈比在堆上分配内存要快,因为(入栈时)操作系统无需为存储新数据去搜索内存空间;其位置总是在栈顶。相比之下,在堆上分配内存则需要更多的工作,这是因为操作系统必须首先找到一块足够存放数据的内存空间,并接着做一些记录为下一次分配做准备。

stack vs heap访问数据

访问堆上的数据比访问栈上的数据慢,因为必须通过指针来访问。现代处理器在内存中跳转越少就越快(缓存)。继续类比,假设有一个服务员在餐厅里处理多个桌子的点菜。在一个桌子报完所有菜后再移动到下一个桌子是最有效率的。从桌子 A 听一个菜,接着桌子 B 听一个菜,然后再桌子 A,然后再桌子 B 这样的流程会更加缓慢。出于同样原因,处理器在处理的数据彼此较近的时候(比如在栈上)比较远的时候(比如可能在堆上)能更好的工作。在堆上分配大量的空间也可能消耗时间。

当你的代码调用一个函数时,传递给函数的值(包括可能指向堆上数据的指针)和函数的局部变量被压入栈中。当函数结束时,这些值被移出栈。

跟踪哪部分代码正在使用堆上的哪些数据,最大限度的减少堆上的重复数据的数量,以及清理堆上不再使用的数据确保不会耗尽空间,这些问题正是所有权系统要处理的。一旦理解了所有权,你就不需要经常考虑栈和堆了,不过明白了所有权的存在就是为了管理堆数据,能够帮助解释为什么所有权要以这种方式工作。

stack vs heap函数调用

当代码调用函数时,值被传入到函数(也包括指向heap的指针),函数本地的变量被压到stack上,当函数运行结束后,这些值会从stack上弹出(出栈)。

所有权存在的原因

所有权可以解决以下问题:

  • 跟踪代码的哪些部分正在使用heap的哪些数据
  • 可以最下滑heap上的重复数据量
  • 清理heap上未使用的数据以免空间不足

一旦熟悉了所有权的,就不需要经常像stack or heap了,所有权存在的主要原因就是管理heap数据。

热门内容