Semorphe

自己管記憶體

要多少自己開,用完自己還。 · ⏱ 約 35 分鐘

你會學到三件事

  1. malloc 在執行時才決定要多大
  2. free 用完要還——不還就是漏
  3. sizeof 問「這個型別佔幾個位元組」

開始之前

C 的陣列大小要在寫程式的時候就決定:

int a[5];       // 五格,寫死

而「要幾格」常常要跑起來才知道。C++ 用 vector,C 用 malloc。

🟢 先把編輯器打開,跟著下面一起做:跟著做

一、開一塊

要多大自己說,單位是位元組:

1int *a = (int*)malloc(5 * sizeof(int));
5取得int的大小×配置個int(整數)的記憶體空間(int(整數)*指標)建立int(整數)指標變數a初始值(*)1

拆開來看:

這一段 意思
sizeof(int) 一個 int 佔幾個位元組(多半是 4)
5 * sizeof(int) 我要 5 個 int 的空間 = 20 位元組
malloc(20) 給我 20 個位元組,回傳它的地址
(int*) 把那個地址當成「指向 int 的指標」

⚠️ malloc 要的是位元組數,不是格數。 寫 malloc(5) 的話你只拿到 5 個位元組——一個 int 都放不滿兩個。

⚠️ 不要寫死 malloc(5 * 4)。 int 不一定是 4 個位元組, 而 sizeof 永遠是對的。

積木盤上它們寫著「取得 … 的大小」與「釋放記憶體 …」; 而底下那個 for 是第 11 課的「計數 … 從 … 到 …」。

二、用起來跟陣列一樣

開出來之後,用起來就跟陣列一樣:

1for (int i = 0; i < 5; i++) {2    a[i] = i * i;3}4printf("%d\n", a[4]);      // 16
05變數i變數i變數i×設定 陣列a的第 [] 格 ←變數a4陣列的第 [] 格格式化輸出 (printf)%d\n,計數i從到(不含)重複使用命名空間std引入函式庫cstdio(C++ 標準輸入輸出)124

方括號對指標一樣能用——因為 a[i] 的定義就是 *(a + i)。

陣列和指標在 C 裡幾乎是同一件事。 差別在於陣列的空間是編譯器給的,malloc 的是你自己要的。

三、🔴 用完要還

malloc 出來的每一塊都要自己還:

1free(a);
變數a釋放記憶體1

不還的話那塊空間一直被佔著,直到程式結束。

一支跑一秒的程式漏一點沒差;一支跑一整天的伺服器漏一點, 一天之後記憶體就滿了。這叫記憶體洩漏。

⚠️ free 之後不要再用它:

1free(a);2printf("%d\n", a[0]);      // 🔴 那塊空間已經不是你的了
變數a變數a0陣列的第 [] 格格式化輸出 (printf)%d\n,釋放記憶體使用命名空間std引入函式庫cstdio(C++ 標準輸入輸出)12

這叫 use-after-free,而它不一定會當掉——它可能印出一個看起來正常的數字, 因為那塊記憶體還沒被別人拿走。這是最難查的一類 bug。

習慣:free(a); 之後馬上 a = NULL;。這樣再用到就會當場當掉,而不是安靜地錯。

四、⚠️ malloc 可能失敗

記憶體不夠的時候 malloc 回 NULL。

要不到的時候它回 NULL——每一次都要問:

1int *a = (int*)malloc(n * sizeof(int));2if (a == NULL) {3    printf("記憶體不夠\n");4    return 1;5}
變數n取得int的大小×配置個int(整數)的記憶體空間(int(整數)*指標)變數aNULL=1回傳格式化輸出 (printf)記憶體不夠\n,如果則建立int(整數)指標變數a初始值(*)使用命名空間std引入函式庫cstdio(C++ 標準輸入輸出)1234

教學程式裡常常省略,而正式的程式不能省。

完成的樣子

這一課的參考做法——程式碼每一行的號碼,就印在對應的那塊積木上。

1#include <cstdio>2using namespace std;3int main() {4    int *a = (int*)malloc(5 * sizeof(int));5    for (int i = 0; i < 5; i++) {6        a[i] = i * i;7    }8    printf("%d\n", a[4]);9    free(a);10    return 0;11}
5取得int的大小×配置個int(整數)的記憶體空間(int(整數)*指標)05變數i變數i變數i×設定 陣列a的第 [] 格 ←變數a4陣列的第 [] 格變數a0回傳釋放記憶體格式化輸出 (printf)%d\n,計數i從到(不含)重複建立int(整數)指標變數a初始值(*)定義函式 回傳型別int(整數)main使用命名空間std引入函式庫cstdio(C++ 標準輸入輸出)1234568910

換你了

改成讀進使用者輸入的 n,開 n 格,填 1 到 n,印出總和。

這就是 vector 幫你做的事。 做完之後你會知道那顆積木背後是什麼。

到編輯器做這一題:練習:讀進 n,開 n 格填 1 到 n,印出總和

做一個

讀進 n,用 malloc 開 n 格,第 i 格填 (i+1) 的平方,印出總和,最後 free。

⚠️ 裁判用的是 5(1+4+9+16+25)。 🔴 malloc(n * sizeof(int)) 裡的 sizeof(int) 不能省—— 省掉它程式照樣跑,而你只開了四分之一的空間。

到編輯器做這一題:做一個:執行時才決定要多大

這一課你做了什麼

如果卡住了

你看到 多半是因為
只有前一兩格是對的 malloc 的參數忘了乘 sizeof(int)
free 之後還印得出東西 那是巧合。那塊記憶體已經不是你的了
程式跑久了越來越慢 漏了 free
double free 錯誤 同一塊 free 了兩次。free 完設 NULL
在編輯器打開這一課 →