核心函式原型
#include <stdlib.h>
void *malloc(size_t size); // 申請 size 位元組,未初始化
void *calloc(size_t nmemb, size_t size); // 申請 nmemb*size,歸零
void *realloc(void *ptr, size_t size); // 調整既有區塊大小
void free(void *ptr); // 釋放,ptr 需為 malloc 系回傳或 NULL
記憶體區域與指標角色
- 堆區:`malloc` 族函式唯一來源,位址由執行期決定,無變數名、無型別。
- 指標:唯一「把持」堆區塊的手柄,儲存於靜態區/棧區/其它堆區塊中。
- 型別轉換:`malloc` 回傳 `void*`,C 語言隱性轉換為目標指標型別;C++ 需顯式 `static_cast`。
三大函式行為細節
| 函式 | 行為 | 常見誤區 |
| `malloc(n)` | 成功返回對齊後位址,失敗返回 `NULL`;內容不定值 | 以為會歸零;忘記檢查 `NULL` |
| `calloc(n, sz)` | 等價 `malloc(n*sz)` + `memset(0)`;溢位檢查更安全 | 以為比 `malloc` 慢很多 (實際差異極小) |
| `realloc(p, n)` | 可能原地擴展、可能移動、可能縮小;`p==NULL` 等價 `malloc(n)` | 直接 `p = realloc(p, n)` 導致移動時舊指標遺失 → 記憶體洩漏 |
realloc 安全模式
void *tmp = realloc(ptr, new_size);
if (!tmp) { /* 處理失敗,ptr 仍有效,可繼續使用或釋放 */ }
ptr = tmp; // 只有成功才更新指標
實戰範例:動態陣列結構體
typedef struct {
int *data;
size_t size; // 當前元素數
size_t capacity; // 已分配容量
} DynArray;
bool dyn_init(DynArray *a) {
a->capacity = 4;
a->size = 0;
a->data = malloc(a->capacity * sizeof(int));
return a->data != NULL;
}
bool dyn_push(DynArray *a, int val) {
if (a->size == a->capacity) {
size_t new_cap = a->capacity * 2;
int *tmp = realloc(a->data, new_cap * sizeof(int));
if (!tmp) return false;
a->data = tmp;
a->capacity = new_cap;
}
a->data[a->size++] = val;
return true;
}
void dyn_free(DynArray *a) {
free(a->data);
a->data = NULL;
a->size = a->capacity = 0;
}
鏈表:動態記憶體的經典載體
typedef struct Node {
int val;
struct Node *next;
} Node;
Node *list_append(Node *head, int val) {
Node *node = malloc(sizeof(Node));
if (!node) return head; // 失敗不修改原鏈表
node->val = val;
node->next = NULL;
if (!head) return node;
Node *cur = head;
while (cur->next) cur = cur->next;
cur->next = node;
return head;
}
void list_free(Node *head) {
while (head) {
Node *tmp = head;
head = head->next;
free(tmp);
}
}
常見錯誤與偵錯工具
| 錯誤類型 | 現象 | 偵測工具 |
| 記憶體洩漏 | 長期運行記憶體單調上升 | Valgrind `--leak-check=full`、AddressSanitizer `-fsanitize=address` |
| 重複釋放 | `free()` 兩次同一指標 → 崩潰/堆損壞 | ASan、Valgrind `invalid free` |
| 釋放後使用 | 讀寫已 `free` 區塊 → 未定義行為 | ASan `heap-use-after-free` |
| 緩衝區溢位 | 寫入超出 `malloc` 大小 | ASan `heap-buffer-overflow`、Electric Fence |
| 未對齊存取 | 某架構 (SPARC/ARM 舊) 存取非對齊位址崩潰 | 編譯器警告、運行時 SIGBUS |
最佳實踐清單
- 永遠檢查回傳值:`if (!p) handle_oom();`
- 配對原則:每個 `malloc`/`calloc`/`realloc` 成功必有對應 `free`,所有權清楚 (誰配置誰釋放,或文件化轉移)。
- 釋放歸零:`free(p); p = NULL;` 防止二次釋放/野指標。
- 柔性陣列成員 (C99):`struct { size_t len; char data[]; }` 單次分配承載結構體+變長資料,減少碎片、提升快取命中。
- 自定義分配器:嵌入式/高效能場景實作 Pool/Slab/Bump Allocator,統一介面 `void* alloc(size_t)` / `void dealloc(void*)`。
- 啟用強化模式:`MALLOC_CHECK_=3` (glibc)、`MallocScribble=1` (macOS)、`PageHeap` (Windows) 開發期捕獲堆損壞。
動態記憶體是 C 語言威力的來源,也是 Bug 溫床。掌握「malloc 族三大金剛、realloc 安全模式、鏈表實戰、ASan/Valgrind 雙重防線」,讓指標在堆區自由馳騁而不失控。