Rust 内存管理如何向初学者发挥作用。
你有没有想过,当你运行一个程序时,你的内存会发生什么?你编写代码的方式又会如何影响系统中的许多其他东西?
该文章将帮助您更多地了解管理内存以及RUST 如何运作这个主题。
目录
1. 栈和堆
在了解 Rust 的功能之前,你需要先了解一些概念。通常,我们有两种类型的内存,分别称为:栈 (Stack) 和 堆 (Heap)。现在我们来介绍一下它们。
1.1 内存:堆栈
栈,顾名思义,工作原理类似堆栈,遵循“后进先出”(LIFO)的原则。分步骤解释会更容易理解:
- 想象一堆盘子;
- 第一个放入的盘子是最后一个被取出的;
- 当调用一个函数时,一块内存会被‘堆叠’在堆栈的顶部;
- 当函数结束时,该块将被“出栈”,从而释放该内存。
通常,编译器(在编译时)知道将存储在堆栈中的值,因为它知道存储需要多少内存。此过程自动发生,所有值都会从内存中删除。
下面是一个例子:
fn main() {
let number = 12; // at this moment the variable has created
println!("{}", number); // 12
} // When the owner (main function) goes out of scope, the value will be dropped
在 Rust 中,我们只需使用 即可创建一些作用域{}
,这会在堆栈中添加具有有限生命周期的层。离开该特定作用域后,内存将被清除,相关信息也会丢失。一个简单易懂的例子是:
fn main() {
{
let number = 12;
println!("{}", number); // 12
}
println!("{}", number); // Cannot find value `number` in this scope
}
1.2 内存:堆
简而言之:堆内存是用于分配可能发生变化的数据的空闲内存空间。
想象一下,您需要在启动程序后存储一些变量,这个变量在编译时没有已知的固定大小,因为它的大小可以变化或者是在内存中的直接分配。
如果上述任何一种情况都符合,我们就知道我们拥有的是堆内存,而不是栈内存。堆内存更灵活,空间也更大。看一下:
let number = Box::new(12); // alocate a integer in heap
let name = String::from("Canhassi"); // alocate a String in heap
在 Rust 中我们有两个types
字符串,&str
和String
。
- &str:根据所写文本具有固定的大小;
- 字符串:具有可以增加、减少和删除的大小。
...这就是为什么String
它存储在堆和&str
堆栈上。
释放堆内存的一种方法是:当存储堆上某些内容的变量离开函数范围(到达函数末尾)时,它将以与堆栈相同的方式被释放。
好了,现在我们对这两种内存类型都有了清晰的认识,那么栈和堆在内存管理上到底有什么区别呢?让我们来看看它们的区别!
2. 借贷检查器
借用检查器是Rust 编译器的一部分,用于检查并确保遵守所有权、借用和生命周期规则。
说实话,一开始我有点不明白它的工作原理,而且我发现这在新 Rustacean 中很常见。不过别担心,我的朋友。我会用最好的方法教你。不过首先,我们先看看下面的代码:
fn main() {
let name = String::from("Canhassi"); // Creating a string variable
print_name(name); // calling the print_name function passing the variable
println!("{}", name); // Error: name borrowed to print_name()
}
fn print_name(name: String) {
println!("{}", name); // print "Canhassi"
}
如果使用该代码运行编译器,您将看到以下错误:
borrow of moved value: name
当我们调用print_name
函数时,name
变量将被移动到另一个作用域,该作用域将成为它的新所有者。“所有者越过作用域”的规则将再次适用。借用检查器确保一旦所有权转移,原始变量将不再用于访问该值。上面的代码中就发生了这种情况。
使用原始变量的另一种方法是使用类似的引用。
fn main() {
let name = String::from("Canhassi"); // Creating a string variable
print_name(&name); // calling the print_name function passing the variable
println!("{}", name); // print "Canhassi"
}
fn print_name(name: &String) {
println!("{}", name); // print "Canhassi"
}
PS:这些借用检查规则仅适用于堆中分配的对象。
3. 预防错误
借用检查器是确保 Rust 内存安全的基础,无需垃圾收集器。
在其他语言(例如 C 和 C++)中,我们(作为开发人员)必须使用某些函数手动释放内存。众所周知,C++ 允许出现内存管理错误,包括内存泄漏。C++ 本身不会导致内存泄漏。相反, C++为程序员提供了极大的灵活性和控制力,如果使用不当,这种自由可能会导致错误。
一个著名的错误是未定义行为,例如,想象一个函数返回一个变量的引用
fn main() {
let number = foo(); // calling foo function
}
fn foo() -> &i32 {
let number = 12; // creating a var number
&number // try return a reference of var number
}
这段代码无法运行,因为 Rust 编译器对内存管理有限制规则。我们不能返回 var 的引用,number
因为这样会超出函数的作用域,导致 varnumber
消失,所以 Rust 不允许这种情况发生。在 C++ 中,我们可以这样做,但这会导致臭名昭著的内存泄漏。
我认为知道 Rust 编译器避免这种类型的错误是很酷的...如果这个主题对你来说很新,我可以告诉你,内存泄漏可能会花费很多钱,而且很难修复,因为从来都不是只有一个内存泄漏。
另一个示例是双重释放,当您尝试释放同一个对象两次时(例如在 C 代码中)就会发生这种情况。
char* ptr = malloc(sizeof(char));
*ptr = 'a';
free(ptr);
free(ptr);
这个主题非常广泛,使用其他语言时也很容易出现类似的错误。不过,我会让你自己研究,并确保在这篇文章的评论区告诉我更多相关信息!
4. 结论
我写这篇文章的目的是更全面地介绍 Rust 的内存管理机制。但为了更全面地了解 Rust 的内存管理,我推荐您阅读《Rust 编程语言》一书的第四章。书中有更多示例和相关内容的讲解。
真心希望这篇文章对你有帮助!!🦀🦀
文章来源:https://dev.to/canhassi/how-rust-memory-management-work-to-beginners-622