C語言-動態記憶體配置 Dynamic Memory Allocation
我們在前兩篇文章分別介紹了指標 (Pointer) 的使用以及鏈結串列 (Linked List),這兩者都是寫程式的基礎。這篇文章要來解釋動態記憶體配置,這樣我們才能靈活地使用鏈結串列。
C語言-動態記憶體配置 Dynamic Memory Allocation

2025.09.12 Antwerp, Belgium
我們在前兩篇文章分別介紹了指標 (Pointer) 的使用以及鏈結串列 (Linked List),這兩者都是寫程式的基礎。這篇文章要來解釋動態記憶體配置,這樣我們才能靈活地使用鏈結串列。
在關於鏈結串列的文章中,我們為了示範指標如何把資料串起來,寫了這樣的程式碼:
struct Node n1, n2, n3;
n1.data = 10;
n1.next = &n2;
// ...
這段程式碼雖然能跑,但在實務上卻有著致命的缺點:我們必須在寫程式的當下,就決定好要有幾個節點。 想像一下,如果你正在寫一個讓玩家把裝備放進背包的遊戲功能,你總不能在程式碼裡先宣告 struct Node item1, item2 ... item1000; 吧? 萬一玩家拿了 1001 個道具怎麼辦?
為了解決「不知道到底需要多少記憶體」的問題,我們必須從「靜態宣告」走向「動態記憶體配置 (Dynamic Memory Allocation)」。
在 C 語言中,程式執行時使用的記憶體大致分為兩個主要區域。
- Stack (堆疊區): 這是編譯器自動管理的區域。我們之前寫的
int a = 5;或是struct Node n1;,這些變數都住在 Stack 裡。它的特色是自動化且速度快,但空間有限。當函數執行結束時,裡面的變數就會被系統回收。 - Heap (堆積區): 這是一個自由區。系統不會主動把變數放在這裡,也不會主動幫你回收。你必須手動向作業系統申請空間,這塊空間就會一直存在,直到你手動歸還為止。
而我們要讓鏈結串列能夠無限生長,就必須學會如何在 Heap 區「申請」與「歸還」土地。
要在 C 語言中使用動態記憶體,我們必須先引入 <stdlib.h> 函式庫。其中最核心的函數就是 malloc (Memory Allocation 的縮寫)。
malloc 的用法很單純:你告訴它你需要多少位元組 (Bytes) 的空間,它就會去 Heap 區找一塊連續的空地,然後把這塊空地的起點地址 (指標)交給你。
#include <stdio.h>
#include <stdlib.h> // 必須引入此函式庫
int main() {
// 假設我們想要動態產生一個整數陣列,大小為 5
// sizeof(int) 會算出一個 int 需要多少記憶體 (通常是 4 bytes)
// 所以 5 * sizeof(int) 就是我們要申請的總空間
int *arr = (int*) malloc(5 * sizeof(int));
// 防呆機制:如果系統記憶體不足,malloc 會回傳 NULL
if (arr == NULL) {
printf("記憶體申請失敗!\n");
return 1;
}
// 申請成功後,可以把它當作一般陣列來用
arr[0] = 100;
arr[1] = 200;
printf("第一個元素: %d\n", arr[0]);
return 0;
}
在上面的程式碼中,malloc有以下幾個要注意的點
sizeof(int)可能會讓人有點困惑。這是因為在不同的系統或編譯器下,資料型態占用的空間不一定相同。因此用sizeof(型態)可以確保程式在任何電腦下都能申請到正確大小的空間,比手寫數字來得好。malloc的任務是在 Heap 區申請一塊空間,它不知道這塊空間要拿來裝整數、字元還是結構體,所以它會回傳的型別是void*(未定義型別的指標)。所以我們通常會在前面加上(int*)或是我們想要的指標型別,將其強制轉型 (Type Casting)。malloc回傳的是一塊記憶體的「地址」,所以用來接住它的變數必須是指標,如果你宣告int arr,則編譯器會報錯。- 你可能會好奇,既然
arr是一個指標,為什麼賦值時不是寫*arr[0] = 100;而是直接寫arr[0] = 100;呢?這是因為在 C 語言中,中括號[]本身就內建了「解引用」的動作。arr[i]其實就是底層*(arr + i)。也就是說,你在寫arr[i]的時候已經包含解引用了,如果你另外加上*,反而會出現記憶體錯誤 (Segmentation Fault)。
現在我們把 malloc 的用到上一篇的鏈結串列中。我們來寫一個專門製造新節點的函數:
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node *next;
};
// 製造新節點的函數
struct Node* createNode(int value) {
// 1. 向系統申請一塊足以容納 struct Node 的記憶體
struct Node *newNode = (struct Node*) malloc(sizeof(struct Node));
if (newNode == NULL) {
printf("記憶體申請失敗!\n");
return NULL;
}
// 2. 將資料填入新申請的節點中
newNode->data = value;
newNode->next = NULL; // 新節點的下一站預設為 NULL
// 3. 回傳這塊新記憶體的地址
return newNode;
}
int main() {
// 現在,我們可以隨心所欲地動態創造節點了!
struct Node *head = createNode(10);
head->next = createNode(20);
head->next->next = createNode(30);
// 印出看看
struct Node *ptr = head;
while (ptr != NULL) {
printf("%d -> ", ptr->data);
ptr = ptr->next;
}
printf("NULL\n");
return 0;
}
這樣一來我們就不需要再提前宣告 n1, n2, n3,只要呼叫 createNode(),記憶體就會在執行當下產生出一個全新的節點。
在 Heap 區借來的記憶體,即使你的程式(或函數)執行結束了,作業系統也不會自動收回。如果你一直 malloc 卻不歸還,你的程式就會把電腦的記憶體吃光,導致程式當機,這就稱為 記憶體洩漏 (Memory Leak)。
要歸還記憶體,我們使用 free() 函數:
int *ptr = (int*) malloc(sizeof(int));
*ptr = 100;
// ... 使用完畢後歸還
free(ptr);
ptr = NULL; // 釋放後將指標設為 NULL,避免變成「懸空指標 (Dangling Pointer)」
如果是鏈結串列,釋放記憶體就需要特別小心,你不能直接 free(head),因為這樣只會釋放第一個節點,後面的節點就會徹底失聯,永遠霸佔著記憶體。你必須用迴圈,一個一個沿著指標把所有節點都 free 掉。
// 釋放整個鏈結串列的函數
void freeList(struct Node* head) {
struct Node* temp; // 準備一個暫存指標
while (head != NULL) {
temp = head; // 1. 先用 temp 抓住目前要準備釋放的節點
head = head->next; // 2. 在炸毀節點前先把 head 移到下一站安全的地方
free(temp); // 3. 把剛才抓住的節點釋放掉
}
// 迴圈結束後,所有的節點都被清空了
printf("鏈結串列已成功釋放!\n");
}
注意到當你之前使用ptr=malloc(32)的時候,系統其實會分配大一點點的空間,並在ptr指向的地方前面塞一個標頭,這個標頭標示著分配的記憶體大小,以及它目前的狀態(比如已分配),因此當你使用的free的時候,free函式就會讀這個標頭,然後它就會知道這裡有多少空間被租去了,再把它的狀態改成空閒。注意到這可能發生的問題是當你之後使用malloc時,你可能會被分配到之前釋放過的空間,這裡面就包含了你以前寫的值。
在 C 語言的 <stdlib.h> 函式庫中,用來處理動態記憶體的其實有好幾個。除了上述介紹的 malloc 之外,還有 calloc 和 realloc。它們各自有不同的專長:
calloc(Contiguous Allocation)跟 malloc 非常像,最大的差別在於:calloc 會自動幫你把申請來的記憶體全部填上 0(歸零)。如果你希望拿到全部初始值都是 0 的記憶體,就可以用 calloc 。這在建立需要預設為 0 的陣列或是計數器時非常方便。你可能會有的疑問是,既然calloc可以保證每次拿到的記憶體空間的位元都是 0,那幹嘛不永遠都是calloc就好,還要用 malloc? 這是因為calloc必須花費額外的效能,逐一走訪這些記憶體位元並寫入 0。
另外,它在語法上需要傳入「兩個」參數:數量與單一單位的大小。
// 用法:calloc(數量, 每個單位的大小);
// 申請 5 個整數的空間,這 5 個格子的初始值都會是 0
int *arr = (int*) calloc(5, sizeof(int));
if (arr != NULL) {
printf("第二個元素預設為: %d\n", arr[1]); // 這裡保證會印出 0
free(arr);
}
了解了 calloc 之後,我們來看看本文的最後一個重要函式realloc。想像一個情境:你原本預估玩家只會撿到 5 個道具,所以用 malloc 申請了大小為 5 的陣列。結果玩家撿到第 6 個道具了,這時候原本的記憶體空間已經裝不下,難道我們要自己手動申請一塊更大的空間、把舊資料用迴圈慢慢複製過去、再把舊空間釋放掉嗎?
C 語言提供了 realloc 來幫我們處理這個「搬家與擴建」的工程。realloc 可以重新調整已經申請的記憶體區塊大小。realloc包含以下兩步:
原地擴建: 如果這塊記憶體後方剛好有足夠的連續空地,它會直接把空地劃給你,原本的資料連動都不用動。
無痛搬家: 如果後方空間不夠,realloc 會在 Heap 區另尋一塊足夠大的新土地,自動幫你把舊資料全部複製過去,然後自動幫你把舊土地 free 掉,最後回傳新土地的地址。
// 假設我們原本有一個大小為 5 的陣列 arr
// 現在我們想把它擴充到可以裝 10 個整數
// 用法:realloc(原本的指標, 新的總大小);
// 我們先用一個「暫存指標」來接 realloc 的結果,而不是直接寫 arr = realloc(arr, ...);
int *temp = (int*) realloc(arr, 10 * sizeof(int));
if (temp == NULL) {
// 如果擴建失敗 (例如系統真的沒記憶體了)
// 原本的 arr 空間和裡面的資料都會安全保留
printf("擴充記憶體失敗!\n");
} else {
// 擴充成功,我們才安心地把擴建後的新地址交接給 arr
arr = temp;
printf("擴充成功!現在可以裝 10 個道具了。\n");
}
結語
本文介紹了動態記憶體配置,我們了解了 Stack 與 Heap 的區別,掌握了 malloc、calloc、realloc 的區別,並學會了如何優雅地釋放鏈結串列,這是對新手來說很重要的觀念。
[embed]C語言-堆疊 Stack 在介紹動態記憶體配置的文章中,我們解釋了 Stack 和 Heap,他們是記憶體空間的不同區域,並且在不同的時機使用。medium.com
메타데이터
- post_id
- 633f55e18cbf
- slug
- c語言-動態記憶體配置-dynamic-memory-allocation-633f55e18cbf
- url
- https://medium.com/@acamvproducingstudio/c%E8%AA%9E%E8%A8%80-%E5%8B%95%E6%85%8B%E8%A8%98%E6%86%B6%E9%AB%94%E9%85%8D%E7%BD%AE-dynamic-memory-allocation-633f55e18cbf
- canonical_url
- https://medium.com/@acamvproducingstudio/c%E8%AA%9E%E8%A8%80-%E5%8B%95%E6%85%8B%E8%A8%98%E6%86%B6%E9%AB%94%E9%85%8D%E7%BD%AE-dynamic-memory-allocation-633f55e18cbf
- author_url
- https://medium.com/@acamvproducingstudio
- status
- ok
- fetched_at
- 2026-08-07 06:05:25