每日一R「06」内存管理

390 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第06天,点击查看活动详情

在前面的课程中,我们学习了 Rust 的所有权、生命周期等比较重要的概念。今天,我们将跟着陈天老师一起学习内存管理,即数据在内存中如何创建、如何存放以及如何销毁。

01-值的创建

内存管理主要是指栈和堆内存的管理。栈内存“分配”和“释放”都很高效,在编译阶段就确定好了,所以它承载的是静态(固定)大小或者生命周期受限(与栈帧生命生命周期绑定)的值。而堆内存则承载的是动态大小或者生命周期超出栈帧存活范围外的值。因此,对任何一门编程语言来说,堆内存管理都是一块非常重要、也非常难管理的内容。

Rust 在设计时对堆内存承载值进行了进一步审视,发现大部分对堆内存的需求在于动态大小,小部分的需求是更长的生命周期。对于前者,Rust 使用胖指针和所有权原则,将堆上动态大小的值与栈绑定在一起。对于后者,Rust 预留了 Box::leak 机制,允许堆上内容的生命周期长于栈帧。

Rust 中,创建值时,根据值的性质,固定长度的创建在栈上,动态大小的创建在堆上。

01.1-struct 在内存中的布局

Rust 创建 struct 时,会根据域的对齐情况进行数据重排序,以提高内存访问效率。对齐的主要原因是,CPU 在加载非对齐数据时性能会急剧下降。

Untitled.png

如果不想 Rust 进行对齐优化,可以通过#[repr]宏告诉编译器,不要进行重排。

01.2-enum 在内存中的布局

Rust 中,enum 是一个标签联合体,它的大小由两部分组成:标签大小,最大类型的大小。

f22b2700de556385efbc44f04dd6b982.webp

01.3-vec 和 String 在内存中的布局

vec 是一个3字节的胖指针:

  • 一个值指向对内存的指针 pointer
  • 在堆上已分配的容量 capacity
  • 目前已占用的长度 length

Untitled 1.png

String 内部是通过 Vec 存储数据的,所以其与 vec 的内存布局一致。

02-值的使用和销毁

02.1-值的使用

我们在前面学习所有权时了解到,变量复制、函数传参时会所有权会转移(Move 语义),对实现了 Copy trait 的类型,值(栈上)会复制,所有权不会转移。

Copy 和 Move 语义的实现都是栈上内存按位做复制。对于原生类型(默认实现 Copy trait)或者栈上的胖指针,Copy 或 Move 都是效率很高的。

堆上动态大小的类型,例如 vec,当长度达到容量后,仍有数据添加进来时,会触发自动扩容。当元素从数组中取出后,也可用 shrink_to_fit 方法来缩小分配的内存空间,来节约内存的使用。

02.2-值的销毁

Rust 中值销毁时是通过 Drop trait 实现的。当 drop 函数调用时,会先释放堆上的内存,再释放栈上的胖指针。当一个复杂数据结构调用 drop 方法时,会依次调用内部组成部分的 drop 方法。

除了内存资源,其他任何资源,例如 Socket、文件、锁等,都可以在 drop 方法中释放。Rust 能够做到如此简单地回收堆资源,完全得益于所有权系统。

本节课程链接:《11|内存管理:从创建到消亡,值都经历了什么?


历史文章推荐