C 語言記憶體管理 (Memory Management)

在 C 語言中,我們對於記憶體有絕對的控制權。這是一把雙面刃:用得好效能飛天,用不好程式崩潰 (Segmentation Fault) 或造成記憶體洩漏 (Memory Leak)。

Stack vs Heap

記憶體主要分為兩區:

  1. Stack (堆疊)
    • 儲存區域變數 (Local Variables)、函式參數。
    • 自動管理:函式結束時自動釋放。
    • 空間較小,存取速度快。
    • 特點:生命週期短,隨函式生滅。
  2. Heap (堆積)
    • 手動管理:程式設計師負責申請 (malloc) 和釋放 (free)。
    • 空間較大,適合儲存動態大小的資料 (例如讀去未知大小的檔案)。
    • 特點:生命週期長,直到被 free 或程式結束才消失。

動態記憶體配置函式

這些函式都在 <stdlib.h> 中。

1. malloc (Memory Allocation)

配置指定 byte 大小的記憶體。注意:它不會初始化內容(裡面可能是垃圾值)。

// 配置一個整數的空間
int* ptr = (int*)malloc(sizeof(int));

// 配置 5 個整數的陣列空間
int* arr = (int*)malloc(5 * sizeof(int));

2. calloc (Contiguous Allocation)

配置記憶體,並將內容全部初始化為 0。速度比 malloc 稍慢,但較安全。

// 配置 5 個整數,且保證 arr[0]...arr[4] 都是 0
int* arr = (int*)calloc(5, sizeof(int));

3. realloc (Re-Allocation)

重新調整已配置記憶體的大小(保留原內容)。

ptr = (int*)realloc(ptr, 10 * sizeof(int)); // 擴充成 10 個 int

4. free (釋放記憶體)

非常重要! 用完的記憶體一定要歸還。

free(ptr);
ptr = NULL; // 最佳實踐:釋放後設為 NULL

常見陷阱 (Common Pitfalls)

動態記憶體管理是 Bug 的溫床,以下是最常見的三種錯誤。

1. 記憶體洩漏 (Memory Leak)

使用了 malloc 卻忘記 free,導致這塊記憶體一直被佔用,直到程式結束。如果是伺服器程式,記憶體會慢慢被吃光。

void leak() {
    int *p = malloc(100);
    // 做事...
    // 糟糕!忘記 free(p) 就離開了
}

2. 重複釋放 (Double Free)

對同一個位址 free 兩次,會導致程式崩潰。

free(ptr);
// ... 一堆程式碼 ...
free(ptr); // 錯誤!這裡會 Crash

解法:釋放後立刻將指標設為 NULL。對 NULL 呼叫 free 是安全的(什麼都不會發生)。

free(ptr);
ptr = NULL;

free(ptr); // 安全,因為 ptr 已經是 NULL

3. 使用已釋放的記憶體 (Use After Free)

記憶體還給系統了,卻還試著去讀寫它。這會導致不可預期的行為。

free(ptr);
*ptr = 10; // 錯誤!這塊地已經不是你的了

最佳實踐 (Best Practices)

  1. 檢查返回值malloc 如果記憶體不足會回傳 NULL,務必檢查。

    int *p = malloc(sizeof(int) * 1000);
    if (p == NULL) {
        fprintf(stderr, "記憶體配置失敗!\n");
        return 1;
    }
    
  2. 誰配置,誰釋放 (Ownership)

    • 如果函式內 malloc,盡量在同一個函式內 free
    • 如果是工廠模式 (Factory Pattern) 產生物件,要在文件註明「呼叫者負責 free」。
  3. 配對寫法: 寫下 malloc 的當下,馬上在下面寫好 free,然後再把邏輯填在中間。

學會自己要記憶體,就要學會自己還。這就是 C 語言的哲學。