理解記憶體佈局是寫出高效、安全 C 程式的基石。本文系統對比堆疊與堆區特性,說明分配/釋放機制、常見錯誤與最佳實務。
| 區段 | 儲存內容 | 管理方式 | 生命週期 |
|---|---|---|---|
| 程式碼區 | 函式機器碼、常數字面值 | 唯讀,系統管理 | 程式結束 |
| 全域/靜態區 | 全域變數、static 變數 | 系統自動分配/釋放 | 程式結束 |
| 堆疊 | 區域變數、函式參數、返回位址 | 編譯器自動(入棧/出棧) | 離開作用域即釋放 |
| 堆區 | 動態分配記憶體 | 程式設計師手動 (malloc/free) | 手動釋放前持續存在 |
ulimit -s 可調),超出導致堆疊溢位。int a[n],但大小受限於剩餘堆疊空間,嵌入式慎用。malloc 實作。malloc/free 產生外部碎片,長期運行需關注記憶體池技術。malloc(size):未初始化。calloc(n, size):歸零,等同 malloc + memset。realloc(ptr, new_size):擴縮容,可能移動記憶體(舊指標失效)。free(ptr),釋放後必須將指標設 NULL,防野指標。int* bad() { int a = 10; return &a; } // a 存於堆疊,函式結束即銷毀
int* good() { int *p = malloc(sizeof(int)); *p = 10; return p; }
// 呼叫端必須 free()
int* also_good() { static int a = 10; return &a; } // 存全域區,程式結束才釋放,不可 free
void bad_alloc(int *p) { p = malloc(100); } // 只修改區域副本
int main() { int *p = NULL; bad_alloc(p); free(p); // p 仍為 NULL,記憶體洩漏 }
void good_alloc(int **p) { *p = malloc(100); }
int main() { int *p = NULL; good_alloc(&p); free(p); }
int* good_alloc2(int *p) { p = malloc(100); return p; }
int main() { int *p = good_alloc2(NULL); free(p); }
# 編譯執行 gcc demo.c -o demo ./demo & ps -u $USER # 找 PID cat /proc/<PID>/maps # 區段映射 cat /proc/<PID>/smaps # 詳細大小、權限、RSS
關鍵觀察點:堆疊位於高位址、堆區在中間、共用庫映射、分頁大小 4 KB。
| 場景 | 建議 |
|---|---|
| 大小固定、生命週期短、< 1 KB | 堆疊 (區域陣列/結構體) |
| 大小動態、長期存活、跨函式共享 | 堆區 (malloc/free) |
| 需在函式間保持狀態、單一實例 | static 全域/區域變數 |
掌握堆疊與堆區的本質差異,能從根源上避免野指標、記憶體洩漏、堆疊溢位等 C 語言經典坑,也是嵌入式與系統級開發的必修功課。