RUST内存管理(一)- 内存与分配回收
内存使用由分配和回收两部门组成。
第一部分内存申请,创建变量请求其所需内存的过程,由程序员实现,如let a = 1
,变量的定义时申请内存和分配使用。
第二部分实现起来就各有区别了。rust中没有使用垃圾回收(garbage collector,GC),也没有采用c的手动释放。而是编译器通过回收策略,
帮助程序员实时的回收不在使用的内存。
分配
RUST变量的创建方式有以下两种:
- 对于编译时已知大小的(固定大小)变量,在stack中创建。
- 对编译时未知大小的变量,在heap中创建。
如果RUST在heap中创建一个变量,返回当前在heap创建的空间地址,并将该内存空间标记为已用,在stack中存储了返回地址。
获取数据时,heap中的变量通过stack中存储的指针在内存中跳转获取对应区域的值,而stack中的变量则直接获取值,以及heap和stack对内存数据的组织区别,因此有:
- 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
。
变量与数据交互的方式
变量与数据交互的方式有一下几种方式:
- move
- 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); }
作者: 咕咚!
出处: https://www.cnblogs.com/linga/
关于作者:专注虚拟化,运维开发,RPA,Rust,Go,Python!
本文版权归作者和博客园共有,禁止*.csdn.net转载,禁止以盈利为目的的转载,转载文章,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接 如有问题, 可邮件(oldsixa@163.com)咨询.
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步