您是否想過當您執行 Rust 程式時RAM會發生什麼情況?您編寫程式碼的方式會如何干擾系統中的許多其他事物?
這篇文章將幫助您了解更多關於 管理記憶體 和 RUST 如何運作 的資訊。
原文出處:https://dev.to/canhassi/how-rust-memory-management-work-to-beginners-622
在了解 rust 的作用之前,您必須先了解一些概念。一般來說,我們有兩種類型的內存,稱為:堆疊和堆。現在我們來介紹一下他們。
堆疊,顧名思義,工作原理就像堆疊,遵循“後進先出”(LIFO)的原則。也許分步驟解釋會更容易:
*想像一下一堆盤子;
*您放入的第一道菜是最後取出的;
當函數被呼叫時,一塊記憶體被**「堆疊」在棧頂;
當函數結束時,該區塊“unstacked”,釋放該記憶體。
通常,編譯器(在編譯時)知道將儲存在堆疊上的值,因為它知道需要儲存多少記憶體。此過程自動發生,所有值都會從記憶體中刪除。
下面是一個例子:
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
}
簡而言之:堆記憶體是一個空閒記憶體空間,用於分配可能更改的資料。
想像一下,您需要在啟動程式後儲存一些變數,該變數在編譯時沒有已知的固定大小,因為它可能有大小變化或是記憶體中的直接分配。
如果上述可能性之一匹配,我們就知道我們有 堆 內存,而不是 堆疊。堆擁有更靈活的記憶體和更大的空間。看一看:
let number = Box::new(12); // alocate a integer in heap
let name = String::from("Canhassi"); // alocate a String in heap
在 Rust 中,我們有兩種字串「類型」:「&str」和「String」。
&str:根據所寫的文字有固定的大小;
字串:具有可以增加、減少和刪除的大小。
……這就是為什麼 String
儲存在堆上,而 &str
儲存在堆疊上。
釋放堆記憶體的一種方法是:當儲存堆上某些內容的**變數離開函數的作用域(到達函數末端)時,它將以相同的方式釋放作為堆疊。
好了,現在我們對這兩種記憶體類型有了一個清晰的認識,但是堆疊和堆在管理記憶體方面有什麼區別呢?讓我們看看這些差異吧!
借用檢查器是 Rust 編譯器的部分,它檢查並確保所有權、借用和生命週期規則得到尊重。
老實說:一開始我在理解它是如何運作的方面遇到了一些問題,我發現這在新的 Rustaceans 中很常見。但別擔心,我的朋友。我會用最好的方式教你。但首先,讓我們先來看看下面的程式碼:
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:這些借用檢查器的規則僅適用於堆中指派的物件**。
借用檢查器對於確保 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
的引用,因為這個函數的作用域將會消失,而 var number
將會消失,所以 Rust 不會讓這種情況發生。在 C++ 中我們可以做到這一點,並且它允許臭名昭著的記憶體洩漏。
我認為知道 Rust 編譯器避免了這種類型的錯誤真是太酷了...如果這個主題對您來說是新的,我可以說內存洩漏可能會花費很多錢並且確實很難修復它,因為絕不只有一次內存洩漏。
其他範例是,雙重釋放,當您嘗試釋放相同物件兩次(例如在 C 程式碼中)時,就會發生這種情況。
char* ptr = malloc(sizeof(char));
*ptr = 'a';
free(ptr);
free(ptr);
這個主題非常廣泛,當您使用其他語言時,很可能會產生類似的錯誤。但是,我會讓您自己進行研究,並確保在這篇文章的評論中告訴我更多!
我寫這篇文章的目的是以更一般的方式展示記憶體管理如何與 Rust 配合使用。但我建議您閱讀《Rust 程式語言》一書的第 4 章( https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html )來獲得更完整的知識。在那裡您將獲得更多示例和更多關於此內容的解釋。
希望這篇文章有幫助! 🦀🦀