Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

stack 與 heap

本集目標

理解 stack 和 heap 的差別,並揭秘第 1 集鑰匙圈比喻的真正含義。

概念說明

記憶體中兩個常見的位置

程式執行時,資料會被放在記憶體裡。這一集先來認識兩個常見的存放位置:stack 和 heap。

堆疊(stack)

  • 函數被呼叫時,通常會用 stack 存放大小在編譯時期就已知的區域變數。
  • 到目前為止學過的整數、浮點數、布林值、char、長度固定的陣列、tuple,以及只包含這些資料的 struct,在作為區域變數時通常都直接放在 stack 上。
  • 大小在編譯時期就已知的資料不一定很小;重點是編譯器事先知道需要保留多少空間。
  • 函數結束時,這次呼叫使用的 stack 空間會一起收回。

堆積(heap)

  • 程式執行時,可以視需要另外取得 heap 空間。
  • 當資料量在執行時期才知道,或可能隨著程式執行而增長時,資料通常會另外放在 heap 上。例如,如果程式需要存下使用者輸入的所有數字,事前可能不知道總共有幾個。
  • 程式會記住之後要到哪裡找到存放在那裡的資料。
  • Rust 的所有權系統會決定何時可以歸還這塊空間。

鑰匙圈比喻揭秘!

還記得第 1 集的鑰匙圈比喻嗎?現在來揭秘它真正的意思:

  • 鑰匙 = 讓程式之後能找到保險箱的資訊。
  • 保險箱 = 另外存放在 heap 上的資料。
  • 鑰匙圈上的裝飾 = 直接帶在鑰匙圈上的資料。

所以當我們說「move 是把鑰匙圈交出去」:

  • 鑰匙和裝飾會一起交給新的擁有者。
  • 保險箱本身留在原處,不需要搬動或重新建立。

為什麼整數是 Copy

整數(i32 等)就像鑰匙圈上的裝飾。複製整數是單純、機械化的操作,所以整數實作了 Copy

有些型別還負責管理另外存放在 heap 上的資料,因此不能用同樣的方式自動複製。這類值在賦值時會 move;需要建立副本時,則要明確呼叫 .clone()。接下來幾集會看到具體的例子。

範例程式碼

#[derive(Debug, Copy, Clone)]
struct StackData {
    x: i32,
    y: i32,
    active: bool,
}

fn main() {
    // 這些區域變數的大小在編譯時期就已知,
    // 所以這些值可以直接放在 stack 上
    let a = 42;    // i32,4 bytes
    let b = 3.14;  // f64,8 bytes
    let c = true;  // bool,1 byte
    let ch = '🦀'; // char,4 bytes
    println!("整數:{},浮點:{},布林:{},字元:{}", a, b, c, ch);

    // struct 直接存放所有欄位,所以也可以直接放在 stack 上
    let data = StackData { x: 10, y: 20, active: true };
    let data2 = data; // Copy!data 還能用
    println!("data = {:?}", data);
    println!("data2 = {:?}", data2);

    // 長度固定的陣列也可以直接放在 stack 上
    let arr = [1, 2, 3, 4, 5];
    println!("陣列:{:?}", arr);

    // tuple 也可以直接放在 stack 上
    let t = (42, true, 'A');
    println!("tuple:{:?}", t);
}

重點整理

  • stack(堆疊):通常直接存放大小在編譯時期就已知的區域變數,例如整數、固定長度的陣列、tuple,以及由這些資料組成的 struct;函數結束時,這次呼叫使用的空間會一起收回。
  • heap(堆積):通常用來存放資料量在執行時期才知道或可能增長的資料;程式會記住之後要到哪裡找到存放在那裡的資料。
  • 鑰匙圈比喻揭秘:鑰匙 = 用來找到資料的資訊、保險箱 = 另外存放在 heap 上的資料、裝飾 = 直接帶著的資料。
  • 整數的複製是單純、機械化的操作,所以整數實作了 Copy
  • 負責管理另外存放在 heap 上資料的型別,在賦值時會 move;需要建立副本時要明確呼叫 .clone()