理解程式在記憶體中的分佈,是寫出高效、無 Bug 代碼的基石。本文系統梳理五大記憶體區域、堆疊與堆的深度對比、存取效率實測,並以經典範例程式標註每個變數的所屬區域。
| 區域 | 內容 | 管理方式 | 生命週期 |
|---|---|---|---|
| 程式碼段 | 函式機器碼、常數字面值 | 系統/連結器 | 程式執行期間 |
| 資料段(已初始化) | 全域/靜態已初始化變數 | 系統 | 程式執行期間 |
| BSS 段(未初始化) | 全域/靜態未初始化變數(歸零) | 系統 | 程式執行期間 |
| 堆疊 | 區域變數、函式參數、返回位址、暫存器保存 | 編譯器自動 | 函式呼叫期間 |
| 堆 | 動態配置記憶體 | 程式設計師手動 | 配置到釋放/程式結束 |
int a = 0; // 資料段(已初始化)
char *p1; // BSS 段(未初始化全域指標)
int main() {
int b; // 堆疊
char s[] = "abc"; // 堆疊(陣列內容複製自常數區)
char *p2; // 堆疊(指標變數本身)
char *p3 = "123456"; // p3 在堆疊,"123456\0" 在常數區
static int c = 0; // 資料段(已初始化靜態)
p1 = malloc(10); // 10 bytes 在堆
p2 = malloc(20); // 20 bytes 在堆
strcpy(p1, "123456"); // "123456\0" 在常數區,編譯器可能合併與 p3 指向同一地址
}
| 維度 | 堆疊 | 堆 |
|---|---|---|
| 配置方式 | 編譯器自動(如區域變數) | 程式設計師手動 malloc/new |
| 釋放方式 | 自動(離開作用域) | 手動 free/delete,否則洩漏 |
| 位址增長 | 向低位址(x86) | 向高位址 |
| 記憶體連續性 | 連續 | 不連續(鏈表管理空閒塊) |
| 大小限制 | Windows 預設 1-2 MB,編譯期固定 | 受限於虛擬記憶體,可達 GB 級 |
| 配置速度 | 極快(指標遞減) | 較慢(遍歷空閒鏈表、分割合併) |
| 碎片問題 | 無 | 易產生外部碎片 |
| 存取效率 | 高(區域性好、指針解引用少) | 稍低(多一次指針間接) |
char s1[] = "aaaaaaaaaa"; // 堆疊陣列
char *s2 = "bbbbbbbbbb"; // 常數區字串,s2 指標在堆疊
// 存取 s1[1]:直接 [ebp - offset] → cl
// 存取 s2[1]:先讀指標到 edx,再 [edx + 1] → al
// 反組譯顯示堆疊陣列少一條指令,較快
EBP(幀指標鏈)返回時逆序彈出:區域變數 → 參數 → 返回位址 → 續行。
free/delete 使用)Windows 提供 VirtualAlloc 直接在進程位址空間保留/認交記憶體,繞過堆管理器,適用於大塊、對齊、需精確控制的場景,但使用複雜度高。
堆疊像去餐廳吃飯:點菜(配置)→ 吃(使用)→ 走人(自動清理),快但自由度低。
堆像自己做菜:買食材(malloc)→ 處理(初始化)→ 吃(使用)→ 洗碗(free),麻煩但靈活、份量大。