「堆與棧的區別」是 C/C++ 面試必考、系統編程核心基石。本文從 **申請方式、大小限制、系統響應、申請效率、存儲內容、訪問效率、分配方式** 七個維度,結合底層機制 (鏈表管理、棧幀結構、寄存器支持) 進行深度對比,建立完整的內存模型心智圖。
| 維度 | 棧 | 堆 |
|---|---|---|
| 申請方式 | 系統自動分配 (編譯器插入指令)int a; // 自動在棧上分配 |
程序員手動申請/釋放malloc/free, new/delete |
| 大小限制 | 固定上限 (Linux 默認 8MB,Windows 1MB), 向**低地址**增長 (連續區域), 超限 → Stack Overflow |
受限於虛擬內存 (理論 4GB/64bit 巨大), 向**高地址**增長 (非連續,鏈表管理), 碎片化風險 |
| 系統響應 | 指針下移即可,極快, 失敗無法恢復 (進程崩潰) |
遍歷空閒鏈表尋找合適塊, 分割/合併塊,更新鏈表, 可失敗返回 NULL/拋異常 |
| 申請效率 | **極高** (硬件指令 push/pop、專用寄存器 RSP/ESP、編譯器優化) |
較低 (庫函數實現、 鏈表遍歷、系統調用擴容、 鎖競爭、碎片整理) |
| 存儲內容 | 函數調用上下文 (棧幀): 返回地址、參數 (右至左)、 局部變量、保存的寄存器、 栈幀指針 RBP/EBP |
動態分配的對象/數組/結構體, 塊頭通常存儲**塊大小** (供 free 使用) |
| 訪問效率 | **更高**:基於 RBP 固定偏移量訪問,無間接引用,CPU 緩存友好 |
需間接引用 (指針解引用), 可能觸發緩存未命中, 但靈活性極高 |
| 分配方式 | 靜態 (編譯期確定大小) + 動態 (alloca/VLA,函數返回自動釋放) |
純動態 (運行期任意大小/生命周期) |
| 碎片化 | 無 (連續、LIFO 釋放) | **外部碎片** (頻繁 malloc/free 產生小洞), 需內存池/分配器優化 |
| 生命周期控制 | 自動 (作用域結束自動銷毀) | 手動 (忘記 free → 內存洩漏, 重複 free → 崩潰,野指針 → UB) |
RSP (Stack Pointer) 指向棧頂,RBP (Base Pointer) 定位棧幀基址。push/pop、call/ret、enter/leave 直接操作棧,單指令周期。
高地址
+------------------+
| 調用者保存的寄存器 |
| 參數 7+ (溢出) |
+------------------+ ← RBP + 16 (參數區)
| 返回地址 (RIP) |
+------------------+ ← RBP + 8
| 舊 RBP (調用者) | ← RBP (當前幀基址)
+------------------+
| 局部變量區 | ← RBP - offset
| (負偏移訪問) |
+------------------+
| 臨時/對齊/紅區 |
低地址 → RSP
RSP - 128 字節為紅區,葉子函數可不調整 RSP 直接使用,極大優化小函數調用。malloc(size) → 對齊 → 查找合適空閒塊 (首次適配/最佳適配) → 分割 (餘塊回鏈表) → 標記已用 → 返回用戶指針 (塊頭後)。free(ptr) → 獲取塊頭 → 標記空閒 → **合併相鄰空閒塊** (關鍵,防碎片) → 加入空閒鏈表。ptmalloc2 (glibc)、jemalloc、tcmalloc、mimalloc,核心思想:**線程緩存 + 多級尺寸類 + 集中式大塊管理**。
char *s1 = "hello"; // 字面量在 .rodata (只讀數據段),s1 在棟
char s2[] = "hello"; // 數組在棧,內容從 .rodata 複製而來
// s1[0] = 'H'; // UB: 段錯誤 (寫只讀段)
// s2[0] = 'H'; // OK: 棧上可寫
char *s = "...":指針在棧,**指向只讀段**,不可修改內容。char s[] = "...":**數組在棧**,內容可修改,大小固定 (含 \0)。s2[i] = *(RBP - offset + i) (單指令);指針 s1[i] = *(s1 + i) (需先讀取指針值,再加載),棟數組略快。std::array/std::vector (小緩衝優化)。malloc/new/std::make_unique/std::make_shared。ulimit -s (Linux) 或鏈接器選項 /STACK (Windows) 擴大棧。alloca/VLA:運行期大小、棧溢出風險不可控、不可移植 (C11 可選、C++ 無標準)。棟是**硬件加速的、確定性的、自動管理的** LIFO 內存區,適合高頻、小塊、短生命周期分配;堆是**靈活的、手動管理的、可能碎片化的**通用內存區,適合大塊、長生命周期、動態大小分配。**理解雙方底層機制 (棧幀/寄存器 vs 空閒鏈表/分配器),才能寫出高性能、穩健的系統級代碼。**