棧實現遞歸是唯一優雅方式。雖編譯器優化將熱點變量放入寄存器,但棧管理幀(局部變量、參數、返回地址、前幀指針)是通用基礎。x86 棧向低地址增長,PUSH 先修改 ESP 再寫入(286 除外)。
高地址 ├── 參數 N ├── ... ├── 參數 1 ├── 返回地址 (EIP) ├── 舊 EBP (調用者幀基址) ├── 局部變量 1 ├── 局部變量 2 ├── ... └── 局部變量 N ← 當前 ESP (棧頂) 低地址
EBP 鏈接形成調用鏈,GDB bt 即沿此鏈遍歷。
int backtrace(void** buffer, int size) {
int n = 0;
int* p = &n;
int ebp = p[1]; // 調用者 EBP
int eip = p[2]; // 返回地址
for (int i = 0; i < size; i++) {
buffer[i] = (void*)eip;
p = (int*)ebp;
ebp = p[0]; // 上一幀 EBP
eip = p[1]; // 上一幀返回地址
}
return size;
}
配合 addr2line -e exe addr 解析源碼行號。
alloca(size) 直接移動棧頂指針分配內存,函數返回時隨幀銷毀自動釋放,無需 free。適用於:
警告:大分配導致棧溢出;不要在循環中調用;返回指針極其危險。
| 維度 | 棧 | 堆 |
|---|---|---|
| 管理方式 | 編譯器/硬件自動 | 程序員手動/分配器 |
| 分配速度 | 極快 (指針移動) | 較慢 (查找/分裂/鎖) |
| 釋放方式 | 自動 (幀銷毀) | 手動 free / GC |
| 大小限制 | 小 (通常 1-8MB) | 大 (虛擬地址空間) |
| 碎片 | 無 | 有 |
| 適用場景 | 局部變量、遞歸、小臨時數據 | 動態大小、長生命周期、跨函數共享 |
brk/mmap),二次切分free(p); free(p);std::string SSO、std::function SBO 利用棧空間避免堆分配棚是調用的舞臺,堆是數據的倉庫。理解幀佈局能寫出 backtrace、alloca 等黑科技;掌握堆管理原理能規避洩露、碎片、UAF。高手總在「棧上能解決就不上堆,堆上必須管好生命周期」。