Skip to main content

Rust for Rustaceans 第一章: 基础

Rust 中变量的值, 变量本身, 指针的存储位置有:

  • 静态内存区

内存抽象模型

变量本身一般存储在栈上, 变量数据中包含值的解释. 栈上的值和值的解释存储在一起, 或者是变量中指针成员指向堆上的值, 指针值就是指针指向的内存地址.

在思考变量的值时, 有两种抽象模型:

  • 高层: 用于在编程时思考和 move, borrow, lifetime 有关的内容时. 这时程序的抽象模型是由一系列的 flow 构成, 这些 flow 就是变量的生命期演化过程.
  • 底层: 用于在编程时思考和 unsafe, raw pointer 有关的内容时. 这时变量就成了值的"存储插槽", 指向这个变量的指针实际就是指向它的值的指针, 并且可以直接解引用获取值.

内存物理模型

栈用于存放函数调用时候形成的帧(frame), 帧是一片连续的内存区域, 每次函数调用都会在栈顶分配对应的帧存储区域. 栈底部就是 main 函数调用.

帧里面包含了这个函数中所有声明的变量和传入参数, 当函数返回后, 这个帧空间被回收.

栈帧的模型也和 lifetime 紧密联系在一起, 即其中的变量 lifetime 至多只能和栈帧的生命期一致. 当然传入参数是引用的除外, 引用的值并非在栈帧内的变量中存储的.

堆内存相当于一个内存池, 它里面存储的值并不和某个 call stack 中的 frame 关联. 在堆上分配的值只有在显示地释放后, 才会走完生命期. 堆上的内存是多个线程共享的, 每个线程有自己的栈空间.

堆上分配的值由于其特点, 可以在多个线程进行传递指针(栈上值是复制), 因此可以在一个线程上分配, 另外一个线程继续使用和处理这个值. 堆上分配的值生命期没有和调用栈上的帧绑定在一起.

Rust 中主要用 Box<T> 进行堆内存分配, 使用它分配内存后, 返回的实际是一个指向堆上的指针. Box 被 Drop 后, 该值的内存也就被释放了.

如果由于某些错误导致堆内存未被释放, 这样的情况就是内存泄漏. 在 Rust 中通过 move, (borrow & lifetime) 规则来确保不会出现这样的情况. 但特殊情况下我们需要进行 "leak", 比如某种配置文件, 整个程序都会去读取它, 则可以使用 Box::leak 来获取一个拥有 'static 生命期的值的指针.

静态内存(和 Unix 进程内存模型中的除了栈和堆之外的区域对应)

当编译程序时, 程序中的某些确定内容被保存在编译好的二进制文件中, 当加载时, 这些内容被加载到静态内存区域内, 包括:

  1. 程序可执行代码(只读)
  2. 已初始化全局变量, 静态变量, 常量, 未初始化全局变量(用 0 预初始化的)

static 生命期的名字就是通过静态内存的名字来的.

在 Rust 的角度看, 所有静态内存区内的值生命期都是 'static 的, 但并非所有 'static 生命期的值都在静态内存区. 但只要拥有 'static 生命期的值, 就可以保证在程序整个生命期内都是有效的.

const 和 static 的区别在于: static 明确规定值的内存, 而 const 实际是在编译器的协助下, 在编译时将使用位置都替换为 const 对应的内容.