正式开端

Rust内存管理

  1. 大部分堆内存的需求在于动态巨细,小部分需求是更长的生命周期
  2. Rust默认将堆内存的生命周期和运用它的栈内存的生命周期绑在一起,并留了个小口儿 leaked 机制,让堆内存在需要的时分,能够有超出帧存活期的生命周期

11|内存管理:从创建到消亡,值都经历了什么?

值的创立

  1. 编译时能够确认巨细的值都会放在栈上,包括 Rust 供给的原生类型比方字符、数组、元组(tuple)等,以及开发者自界说的固定巨细的结构体(struct)、枚举(enum) 等
  2. 假如数据结构的巨细无法确认,或者它的巨细确认但是在运用时需要更长的生命周期,就最好放在堆上

struct

  1. Rust 在内存中排布数据时,会依据每个域的对齐(aligment)对数据进行重排,使其内存巨细和拜访功率最好

  2. C 言语会对结构体对齐的规则

    a. 首先确认每个域的长度和对齐长度,原始类型的对齐长度和类型的长度一致

    b. 每个域的起始位置要和其对齐长度对齐,假如无法对齐,则添加 padding 直至对齐

    c. 结构体的对齐巨细和其最大域的对齐巨细相同,而结构体的长度则四舍五入到其对齐的倍数

11|内存管理:从创建到消亡,值都经历了什么?

Rust 编译器默以为开发者优化结构体的摆放,但你也能够 运用#[repr] 宏,强制让 Rust 编译器不做优化 ,和 C 的行为一致,这样,Rust 代码能够方便地和 C 代码无缝交互

enum

  1. 在 Rust 下它是一个标签联合体(tagged union),它的巨细是标签的巨细加上最大类型的长度
  2. 依据方才说的三条对齐规则,tag 后的内存,会依据其对齐巨细进行对齐。一般而言,64 位 CPU 下,enum 的最大长度是:最大类型的长度 + 8

11|内存管理:从创建到消亡,值都经历了什么?

以下代码能够打印常见数据结构的巨细

use std::collections::HashMap;
use std::mem::size_of;
enum E {
    A(f64),
    B(HashMap<String, String>),
    C(Result<Vec<u8>, String>),
}
// 这是一个声明宏,它会打印各种数据结构本身的巨细,在 Option 中的巨细,以及在 Result 中的巨细
macro_rules! show_size {
    (header) => {
        println!(
            "{:<24} {:>4}    {}    {}",
            "Type", "T", "Option<T>", "Result<T, io::Error>"
        );
        println!("{}", "-".repeat(64));
    };
    ($t:ty) => {
        println!(
            "{:<24} {:4} {:8} {:12}",
            stringify!($t),
            size_of::<$t>(),
            size_of::<Option<$t>>(),
            size_of::<Result<$t, std::io::Error>>(),
        )
    };
}
fn main() {
    show_size!(header);
    show_size!(u8);
    show_size!(f64);
    show_size!(&u8);
    show_size!(Box<u8>);
    show_size!(&[u8]);
    show_size!(String);
    show_size!(Vec<u8>);
    show_size!(HashMap<String, String>);
    show_size!(E);
}
/*
Type                        T    Option<T>    Result<T, io::Error>
----------------------------------------------------------------
u8                          1        2           16
f64                         8       16           16
&u8                         8        8           16
Box<u8>                     8        8           16
&[u8]                      16       16           24
String                     24       24           32
Vec<u8>                    24       24           32
HashMap<String, String>    48       48           56
E                          56       56           64
*/

Option 合作带有引用类型的数据结构,比方 &u8、Box、Vec、HashMap ,没有额定占用空间

Option 复用了引用类型的第一个域(是个指针),当其为 0 时表明 None

Option<&u8>中,将指针作为tag运用,指针为0时,表明没有数据,也即None; 不然,表明有数据,则是Some

vec<T> 和 String

  1. Vec<T>结构是 3 个 word 的胖指针

    a. 指向堆内存的指针 pointer

    b. 分配的堆内存的容量 capacity

    c. 数据在堆内存的长度 length

    11|内存管理:从创建到消亡,值都经历了什么?

  2. 很多动态巨细的数据结构,在创立时都有相似的内存布局:栈内存放的胖指针,指向堆内存分配出来的数据

值的运用

  1. 对 Rust 而言,一个值假如没有完成 Copy,在赋值、传参以及函数回来时会被 Move
  2. Copy 和 Move 在内部完成上,都是浅层的按位做内存仿制,只不过 Copy 答应你拜访之前的变量,而 Move 不答应

11|内存管理:从创建到消亡,值都经历了什么?

  1. 无论是 Copy 还是 Move,它的功率都是非常高的
  2. 一般咱们主张在栈上不要放大数组
  3. 动态数组数据添加会导致内存运用率低下,运用 shrink_to_fit 节约内存

值的毁掉

当一个值要被开释,它的 Drop trait 会被调用

简略类型的开释

11|内存管理:从创建到消亡,值都经历了什么?

  1. 变量 greeting 是一个字符串,在退出作用域时,其 drop() 函数被主动调用
  2. 开释堆上包括 “hello world” 的内存
  3. 然后再开释栈上的内存

复杂数据结构的开释

  1. 比方一个结构体,那么这个结构体在调用 drop() 时,会顺次调用每一个域的 drop() 函数
  2. 假如域又是一个复杂的结构或者集合类型,就会递归下去,直到每一个域都开释洁净

11|内存管理:从创建到消亡,值都经历了什么?

  1. student 变量是一个结构体,有 name、age、scores
  2. 其中 name 是 String,scores 是 HashMap,它们本身需要额定 drop()
  3. 又由于 HashMap 的 key 是 String,所以还需要进一步调用这些 key 的 drop()
  4. 整个开释顺序从内到外是:先开释 HashMap 下的 key,然后开释 HashMap 堆上的表结构,最终开释栈上的内存

堆内存开释

所有权机制规定了,一个值只能有一个所有者,所以在开释堆内存的时分,便是单纯调用 Drop trait

开释其他资源

Rust 对所有的资源都有很好的 RAII 支撑。

小结

11|内存管理:从创建到消亡,值都经历了什么?

好用链接

  1. 数据对齐
  2. String 源码
  3. Vec<T>结构 源码
  4. Rust cheats 快速手册
  5. RAII
  6. RFC
  7. 生命周期概念 帖子

精选问答

  1. Result<String, ()> 占用多少内存?为什么?

    a. 引用类型的第一个域是个指针,而指针是不可能等于 0 的,咱们能够复用这个指针:当其为 0 时,表明 None,不然是 Some

    b. 关于 Result<String, ()> 也是如此,String 第一个域是指针,而指针不能为空,所以当它为空的时分,正好能够表述 Err(())

  2. rust 中的 feature 是干什么用的,怎样开发?

    a. feature 用作条件编译,你能够依据需要选择运用库的某些 feature。它的优点是能够让编译出的二进制比较灵活,依据需要装入不同的功用

    b. 界说 feature,你能够看 cargo book:doc.rust-lang.org/cargo/refer…

    c. 以下是个简略例子

    在 cargo.toml 中,能够界说:
    [features] 
    filter = ["futures-util"] // 界说 filter feature,它有额定对 futures-util 的依靠。 
    [dependencies] 
    futures-util = { version = "0.3", optional = true } // 这个 dep 声明成 optional 
    在 lib.rs 中: 
    #[cfg(feature = "filter")] 
    pub mod filter_all; // 只有编译 feature filter 时,才引进 mod feature_all 编译