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 @   咕咚!  阅读(1655)  评论(2编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示