06-所有权与作用域
部分内容摘编自:https://bornforthis.cn/column/Rust/02.html(著作权归AI悦创所有)
所有权
Rust 的核心功能(之一)是 所有权(ownership)。 在第一节课中,我们了解了 Rust 语言里的值有两大类:一类是固定内存长度(简称固定尺寸)的值,比如 i32、u32、由固定尺寸的类型组成的结构体等;另一类是不固定内存长度(简称非固定尺寸)的值,比如字符串 String。这两种值的本质特征完全不一样。而怎么处理这两种值的差异,往往是语言设计的差异性所在。
就拿数字类型来说,C、C++、Java 这些语言就明确定义了数字类型会占用内存中的几个字节,比如 8 位,也就是一个字节,16 位,也就是两个字节。而 JavaScript 这种语言,就完全屏蔽了底层的细节,统一用一个 Number 表示数字。Python 则给出了 int 整数、float 浮点、complex 复数三种数字类型。
Rust 语言因为在设计时就定位为一门通用的编程语言(对标 C++),它的应用范围很广,从最底层的嵌入式开发、OS 开发,到最上层的 Web 应用开发,它都要兼顾。所以它的数字类型不可避免地就得暴露出具体的字节数,于是就有了 i8、i16、i32、i64 等类型。
前面我们说到,一种类型如果具有固定尺寸,那么它就能够在编译期做更多的分析。实际上固定尺寸类型也可以用来管理非固定尺寸类型。具体来说,Rust 中的非固定尺寸类型就是靠指针或引用来指向,而指针或引用本身就是一种固定尺寸的类型。
1. 栈与堆
现代计算机会把内存划分为很多个区。比如,二进制代码的存放区、静态数据的存放区、栈、堆等。
栈上的操作比堆高效,因为栈上内存的分配和回收只需移动栈顶指针就行了。这就决定了分配和回收时都必须精确计算这个指针的增减量,因此栈上一般放固定尺寸的值。另一方面,栈的容量也是非常有限的,因此也不适合放尺寸太大的值,比如一个有 1000 万个元素的数组。
那么非固定尺寸的值怎么处理呢?在计算机体系架构里面,专门在内存中拿出一大块区域来存放这类值,这个区域就叫“堆”。
2. 栈空间与堆空间
在一般的程序语言设计中,栈空间都会与函数关联起来。每一个函数的调用,都会对应一个帧,也叫做 frame 栈帧,就像图片栈空间里的方块 main、fn1、fn2 等。一个函数被调用,就会分配一个新的帧,函数调用结束后,这个帧就会被自动释放掉。因此栈帧是一个运行时的事物。函数中的参数、局部变量之类的资源,都会放在这个帧里面,比如图里 fn2 中的局部变量 a,这个帧释放时,这些局部变量就会被一起回收掉。
!
当变量离开作用域后,Rust 自动调用 drop
函数并清理变量的堆内存。不过图 4-2 展示了两个数据指针指向了同一位置。这就有了一个问题:当 s2
和 s1
离开作用域,它们都会尝试释放相同的内存。这是一个叫做 二次释放(double free)的错误,也是之前提到过的内存安全性 bug 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
为了确保内存安全,在 let s2 = s1;
之后,Rust 认为 s1
不再有效,因此 Rust 不需要在 s1
离开作用域后清理任何东西。
以上为摘编内容