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 进程内存模型中的除了栈和堆之外的区域对应)
当编译程序时, 程序中的某些确定内容被保存在编译好的二进制文件中, 当加载时, 这些内容被加载到静态内存区域内, 包括:
- 程序可执行代码(只读)
- 已初始化全局变量, 静态变量, 常量, 未初始化全局变量(用 0 预初始化的)
static 生命期的名字就是通过静态内存的名字来的.
在 Rust 的角度看, 所有静态内存区内的值生命期都是 'static 的, 但并非所有 'static 生命期的值都在静态内存区. 但只要拥有 'static 生命期的值, 就可以保证在程序整个生命期内都是有效的.
const 和 static 的区别在于: static 明确规定值的内存, 而 const 实际是在编译器的协助下, 在编译时将使用位置都替换为 const 对应的内容.