RUST内存管理(一)- 内存与分配回收

内存使用由分配和回收两部门组成。

第一部分内存申请,创建变量请求其所需内存的过程,由程序员实现,如let a = 1,变量的定义时申请内存和分配使用。

第二部分实现起来就各有区别了。rust中没有使用垃圾回收(garbage collector,GC),也没有采用c的手动释放。而是编译器通过回收策略
帮助程序员实时的回收不在使用的内存。

分配

RUST变量的创建方式有以下两种:

  1. 对于编译时已知大小的(固定大小)变量,在stack中创建。
  2. 对编译时未知大小的变量,在heap中创建。

如果RUST在heap中创建一个变量,返回当前在heap创建的空间地址,并将该内存空间标记为已用,在stack中存储了返回地址。

获取数据时,heap中的变量通过stack中存储的指针在内存中跳转获取对应区域的值,而stack中的变量则直接获取值,以及heap和stack对内存数据的组织区别,因此有:

  1. stack创建,复制,删除变量的性能优于heap。

回收

如果忘记回收了会浪费内存。如果过早回收了,将会出现无效变量。如果重复回收,这也是个 bug。我们需要精确的为一个 allocate 配对一个 free。

Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域后就被自动释放。

变量作用域

rust中的变量生命周期和变量所在的作用域有关,变量的作用域从定义变量开始到当前作用域结束之前,此范围内可用。如下:

fn main() {
    let s = "hello"; // 以硬编码编译到程序中。

    {
        let a = "world"; // a 的作用域时当前{}内
        println!("{a}");
    } // a的作用域结束,a不在有效,a内存将回收。 // Rust 在结尾的 } 处自动调用 drop。
    
    println!("{s}");
    println!("{a}"); // a不可调用。
    
    /// 变量作用域
    /// 检查
    /// PS D:\project\rust\rust_stu> cargo check
    ///     Checking rust_stu v0.1.0 (D:\project\rust\rust_stu)
    /// error[E0425]: cannot find value `a` in this scope
    ///     --> src\main.rs:10:16
    ///     |
    ///  10 |     println!("{a}");
    ///     |                ^ help: a local variable with a similar name exists: `s`
    /// 
    /// For more information about this error, try `rustc --explain E0425`.
    /// error: could not compile `rust_stu` due to previous error

} // s的作用域结束,s不在有效,s内存将回收。

a离开作用域的时候。当变量离开作用域,Rust为我们调用一个特殊的函数。这个函数叫做drop
在这里a可以放置释放内存的代码。Rust在结尾的}处自动调用drop

变量与数据交互的方式

变量与数据交互的方式有一下几种方式:

  1. move
  2. clone

stack中的变量-拷贝

变量的类型都是已知大小的,可以存储在栈中,并且当离开作用域时被移出栈,如果代码的另一部分需要在不同的作用域中使用相同的值,
可以快速简单地复制它们来创建一个新的独立实例。

fn main() {
    let s = "hello"; // 以硬编码编译到程序中。变量在stack中创建。
    println!("{s}");
} // s的作用域结束,s不在有效,s内存将回收。

如果一个变量创建在stack中,通过该变量赋值时,都会在stack中复制一份新的值。如下:

fn main() {
    let x = 5;
    let y = x; // 在stack中复制一个5,所有权归到y。
    println!("{x}");
    println!("{y}");
}

heap中的变量-移动

编译时未知变量的大小,会将其创建在heap中,并将heap中的内存地址返回存在stack中。

fn main() {
    let mut s = String::from("hello"); // 在heap中创建。
    s.push_str(", world!"); // push_str() 在字符串后追加字面值
    println!("{s}");
} // s的作用域结束,s不在有效,s内存将回收。

如果一个变量创建在heap中,通过该变量赋值时,都会使该变量失效。如下:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1;

    println!("{}, world!", s1);
    /// $ cargo run
    ///     Compiling ownership v0.1.0 (file:///projects/ownership)
    /// error[E0382]: borrow of moved value: `s1`
    ///  --> src/main.rs:5:28
    ///   |
    /// 2 |     let s1 = String::from("hello");
    ///   |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
    /// 3 |     let s2 = s1;
    ///   |              -- value moved here
    /// 4 |
    /// 5 |     println!("{}, world!", s1);
    ///   |                            ^^ value borrowed here after move
    ///
    /// For more information about this error, try `rustc --explain E0382`.
    /// error: could not compile `ownership` due to previous error
}

在heap创建变量时,内存中的数据结构如下:

         stack:s1                 heap
     ---------------        ----------------
    / name / value /       / index / value /
     ---------------        ----------------
    /  ptr /   ===/======> /   0   /   h   /
     ---------------        ----------------
    /  len /   5  /        /   1   /   e   /
     ---------------        ----------------
    /  cap /  5   /        /   2   /   l   /
     ---------------        ----------------
                           /   3   /   l   /
                            ----------------
                           /   4   /   o   /
                            ----------------

let s2 = s1之后,并不会复制heap中的数据,而是将stack中s1的数据移动到s2中,并使s1失效,离开作用域后不在需要清理任何东西,
避免二次释放heap中的该区域,引起bug。

stack中的变量则不同。

Rust 有一个叫做 Copy trait 的特殊标注,可以用在类似整型这样的存储在栈上的类型上(第 10 章详细讲解 trait)。如果一个类型实现了
Copy trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。Rust 不允许自身或其任何部分实现了 Drop trait 的类型使用 Copy trait。
如果我们对其值离开作用域时需要特殊处理的类型使用 Copy 标注,将会出现一个编译时错误。要学习如何为你的类型添加 Copy 标注以实现该 trait,
请阅读附录 C 中的 “可派生的 trait”。

实现了Copy trait的类型:

  • 所有整数类型,比如 u32。
  • 布尔类型,bool,它的值是 true 和 false。
  • 所有浮点数类型,比如 f64。
  • 字符类型,char。
  • 元组,当且仅当其包含的类型也都实现 Copy 的时候。比如,(i32, i32) 实现了 Copy,但 (i32, String) 就没有。

heap中的变量-克隆

如果确实需要赋值heap中的数据,且时原变量有效,则通过clone函数实现。

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("s1 = {}, s2 = {}", s1, s2);
}
posted @ 2022-07-22 17:03  咕咚!  阅读(1757)  评论(2)    收藏  举报