您是否想過當您執行 Rust 程式RAM會發生什麼情況?您編寫程式碼的方式會如何干擾系統中的許多其他事物?

這篇文章將幫助您了解更多關於 管理記憶體RUST 如何運作 的資訊。

原文出處:https://dev.to/canhassi/how-rust-memory-management-work-to-beginners-622

1. Stack and Heap

在了解 rust 的作用之前,您必須先了解一些概念。一般來說,我們有兩種類型的內存,稱為:堆疊和堆。現在我們來介紹一下他們。

1.1 記憶體:Stack

堆疊,顧名思義,工作原理就像堆疊,遵循“後進先出”(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
}

1.2 記憶體:Heap

簡而言之:記憶體是一個空閒記憶體空間,用於分配可能更改的資料。

想像一下,您需要在啟動程式後儲存一些變數,該變數在編譯時沒有已知的固定大小,因為它可能有大小變化或是記憶體中的直接分配。

如果上述可能性之一匹配,我們就知道我們有 內存,而不是 堆疊。堆擁有更靈活的記憶體和更大的空間。看一看:

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 儲存在堆疊上。

釋放堆記憶體的一種方法是:當儲存堆上某些內容的**變數離開函數的作用域(到達函數末端)時,它將以相同的方式釋放作為堆疊。

好了,現在我們對這兩種記憶體類型有了一個清晰的認識,但是堆疊和堆在管理記憶體方面有什麼區別呢?讓我們看看這些差異吧!

2. 借用檢查器

借用檢查器是 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:這些借用檢查器的規則僅適用於堆中指派的物件**。

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 的引用,因為這個函數的作用域將會消失,而 var number 將會消失,所以 Rust 不會讓這種情況發生。在 C++ 中我們可以做到這一點,並且它允許臭名昭著的記憶體洩漏。

我認為知道 Rust 編譯器避免了這種類型的錯誤真是太酷了...如果這個主題對您來說是新的,我可以說內存洩漏可能會花費很多錢並且確實很難修復它,因為絕不只有一次內存洩漏。

其他範例是,雙重釋放,當您嘗試釋放相同物件兩次(例如在 C 程式碼中)時,就會發生這種情況。

char* ptr = malloc(sizeof(char));

*ptr = 'a';
free(ptr);
free(ptr);

這個主題非常廣泛,當您使用其他語言時,很可能會產生類似的錯誤。但是,我會讓您自己進行研究,並確保在這篇文章的評論中告訴我更多!

4。結論

我寫這篇文章的目的是以更一般的方式展示記憶體管理如何與 Rust 配合使用。但我建議您閱讀《Rust 程式語言》一書的第 4 章( https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html )來獲得更完整的知識。在那裡您將獲得更多示例和更多關於此內容的解釋。

希望這篇文章有幫助! 🦀🦀


共有 0 則留言