動態內存分配是 C 語言核心能力,亦是 Bug 溫床。本文從用法規範、堆/棧區別、分配器內部結構、free 釋放機制四個維度,徹底講透 malloc/free。
char *ptr = (char*)malloc(100 * sizeof(char)); if (ptr == NULL) exit(1); // ① 必檢返回值 gets(ptr); // 業務使用 free(ptr); // ② 用完必釋 ptr = NULL; // ③ 釋放後置 NULL 防野指針
四大鐵律: 配對使用、檢查失敗、釋放置 NULL、強制類型轉換(兼容 C++/舊編譯器)。
| 特性 | 棧 | 堆 |
|---|---|---|
| 管理者 | 編譯器自動 | 程序員手動 / 分配器 |
| 存放內容 | 函數參數、局部變量、返回地址 | 動態分配塊 |
| 分配速度 | 極快 (指針移動) | 較慢 (鏈表遍歷/分裂/合併) |
| 碎片問題 | 無 | 外部/內部碎片 |
| 生長方向 | 向下 (高地址→低地址) | 向上 (低地址→高地址) |
| 大小限制 | 較小 (典型 1-8MB) | 大 (虛擬地址空間) |
關鍵認知: 函數內 malloc 分配的內存**不會**隨函數返回自動釋放!指針在棧上,內容在堆上,必須顯式 free。
實際分配塊 = 管理頭 + 用戶數據區。管理頭典型結構:
struct mem_control_block {
int is_available; // 1=空閒, 0=已分配
int size; // 用戶數據區大小 (不含頭)
// 可能包含:前向/後向指針、魔數校驗、對齊填充
};
malloc 從空閒鏈表尋找合適塊,標記 is_available=0,返回 **用戶數據區首地址**(即管理頭之後)。
void free(void *ptr) {
if (!ptr) return; // 釋放 NULL 安全無操作
struct mem_control_block *mcb =
(struct mem_control_block*)((char*)ptr - sizeof(struct mem_control_block));
mcb->is_available = 1; // 標記為空閒
// 可能觸發:相鄰塊合併、歸還給 OS (munmap/sbrk)、放入快取
}
核心邏輯: 指針回退管理頭大小 → 修改標記 → 合併/歸還。**因此指針必須指向用戶區首地址**,偏移釋放會破壞管理頭導致崩潰。
free 時崩潰或靜默腐蝕free,解析管理頭觸發未定義行為malloc/free 實現內存池、檢測洩漏_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF)