← Back to list

C語言-動態記憶體配置 Dynamic Memory Allocation

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

Ac Studio · 2026-05-02 13:57 · 151 claps · 10.6 min read
#程式設計 #資工 #計算機概論 #電機 #c語言
Open on Medium ↗

C語言-動態記憶體配置 Dynamic Memory Allocation

2025.09.12 Antwerp, Belgium

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 語言中,程式執行時使用的記憶體大致分為兩個主要區域。

  1. Stack (堆疊區): 這是編譯器自動管理的區域。我們之前寫的 int a = 5; 或是 struct Node n1;,這些變數都住在 Stack 裡。它的特色是自動化且速度快,但空間有限。當函數執行結束時,裡面的變數就會被系統回收。
  2. 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有以下幾個要注意的點

  1. sizeof(int) 可能會讓人有點困惑。這是因為在不同的系統或編譯器下,資料型態占用的空間不一定相同。因此用 sizeof(型態) 可以確保程式在任何電腦下都能申請到正確大小的空間,比手寫數字來得好。
  2. malloc的任務是在 Heap 區申請一塊空間,它不知道這塊空間要拿來裝整數、字元還是結構體,所以它會回傳的型別是 void* (未定義型別的指標)。所以我們通常會在前面加上 (int*) 或是我們想要的指標型別,將其強制轉型 (Type Casting)。
  3. malloc回傳的是一塊記憶體的「地址」,所以用來接住它的變數必須是指標,如果你宣告 int arr,則編譯器會報錯。
  4. 你可能會好奇,既然 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 之外,還有 callocrealloc。它們各自有不同的專長:

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");
}

結語

本文介紹了動態記憶體配置我們了解了 StackHeap 的區別,掌握了 malloccallocrealloc 的區別,並學會了如何優雅地釋放鏈結串列,這是對新手來說很重要的觀念。

[embed]C語言-指標 Pointer 在 RAM 當中,記憶體是以位元組 (Byte) 為最小定址單位的,每一個位元組都會對應到一個唯一的記憶體地址 (Memory Address)。當我們在 C 語言中宣告一個變數時,編譯器與作業系統就會根據該變數的資料型態 (比如…medium.com

[embed]C語言-鏈結串列 Linked List 介紹完基本的指標後,在深入複雜的資料結構之前,我們必須先認識陣列。陣列是一種線性資料結構,它在電腦記憶體中佔用一塊連續的空間,用來儲存相同型別的資料。你可以把它想像成一排整齊編號的置物櫃,只要知道號碼(索引),就能立刻拿到裡面的東西。medium.com

[embed]搞懂演算法的時間和空間複雜度 想像你是一個剛入職的軟體工程師,你老闆叫你寫一個功能,檢查今天的訂單裡面有沒有重複購買的客戶。於是聰明的你把第一筆訂單拿出來,和剩下的訂單比對;再把第二筆訂單拿出來,繼續和剩下的訂單比對,以此類推。你拿了100筆資料做測試,完美。於是程式碼…medium.com

[embed]C語言-雙重指標與函式指標 本文需要的先備知識為 (1). 指標 (2). 動態記憶體配置。在介紹指標的文章中我們介紹了指標最基本的知識。指標變數可以儲存變數的地址,然後我們可以再去讀這個地址當中的值,如下所示medium.com

[embed]C語言-堆疊 Stack 在介紹動態記憶體配置的文章中,我們解釋了 Stack 和 Heap,他們是記憶體空間的不同區域,並且在不同的時機使用。medium.com

[embed]歡迎來到 Ac Studio!第一次來請先讀這篇 大家好!為了幫助各位讀者快速找到自己需要的內容,我們製作了這張知識地圖。如圖所示,目前我們的技術文章主要分為 8 個大類。希望這張地圖能幫助你快速上手,找到需要的資源。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