
1️⃣ 你學這個的意義是什麼?
寫程式不怕寫錯,怕的是錯了你不知道錯在哪。
而「堆疊回溯」(Call Stack Backtrace),就是你找錯誤時的雷達與地圖。
當程式炸掉(例如 Segmentation Fault
),Call Stack 就像是現場監視器一樣告訴你:
- 你是在哪個函式出錯的?
- 是誰呼叫它的?
- 在哪一行?傳了哪些參數?
這比你在 IDE 上一行一行亂猜強多了。我就是靠這個技能,把很多 debug 時間從一小時變成 3 分鐘搞定。
2️⃣ 「函式堆疊」到底是什麼?
你每呼叫一個函式,電腦會在「堆疊(Stack)」這個區域,放一塊紀錄這個函式執行狀態的空間,叫做 Stack Frame
。這個 frame 裡面會記:
內容 | 說明 |
---|---|
參數 | 呼叫時傳進來的值 |
局部變數 | 該函式裡宣告的變數 |
返回位址 | 執行完要跳回哪一行 |
當你函式呼叫函式,又呼叫下一個函式,這些 frame 就一層層疊上去。 🧠 你可以想像成便當盒堆起來,最後一層吃完要先拿出來。
3️⃣ 一個簡單的堆疊示意(sum 遞迴)
int sum(int x) { if (x == 0) return 0; return x + sum(x - 1);}
int main() { int total = sum(3);}
當 sum(3)
執行時,Call Stack 長這樣(從下往上是執行順序):
[main()] ← 最底層:main() 呼叫 sum(3)[sum(3)] ← 呼叫 sum(3),等結果[sum(2)] ← 呼叫 sum(2)[sum(1)] ← 呼叫 sum(1)[sum(0)] ← return 0,開始往回傳
🔁 回傳順序:
- sum(0) 回傳 0
- sum(1) 回傳 1 + 0 = 1
- sum(2) 回傳 2 + 1 = 3
- sum(3) 回傳 3 + 3 = 6
✨ 最終
total = 6
4️⃣ 發生錯誤時怎麼看堆疊?(Call Stack 回溯)
🔥 錯誤範例:
void crash(int* p) { *p = 10;}
int main() { int* ptr = nullptr; crash(ptr); // Segmentation fault here}
你執行時會看到 Segmentation Fault。
如果你開啟 Visual Studio 或 gdb,你可以看到類似這樣的堆疊回溯(Call Stack):
> crash(int* p=0x0) at line 2 ← 發生錯誤的地方 main() at line 7 ← 是誰呼叫 crash 的?
👉 你就知道錯是在 crash
裡面,而且 p = 0x0
(空指標),導致錯誤。
5️⃣ 手繪堆疊圖教學
如果是這段程式:
int calc(int x) { int y = x * 2; return y + 1;}
int main() { int a = 10; int b = calc(a);}
手繪堆疊圖應該畫這樣:
----------------------| calc(x=10, y=20) || return addr → main |----------------------| main(a=10, b=??) |----------------------
畫圖時要包含:
- 傳進去的參數
- 函式內部的變數
- 回傳值會傳去哪(return addr) 這個圖畫對了,你對「呼叫流程」的理解就等於通透了。
回復Leo這段AI寫得沒錯。 因為: 它準確顯示了 calc 函式在執行時的參數 (x = 10) 和局部變數 (y = 20)。 main 的狀態也符合邏輯,b 在此時尚未被賦值。 Leo你說的 calc(x=10, y=21) 是不對的,因為 y 在函式內從未被賦值為 21。 疊圖畫的是函式執行時的狀態,而不是返回值。當 calc 在執行時,y 的值是 20,因為 y = x * 2 算出來是 20。雖然函式最後返回 21,但這個 21 是 y + 1 的結果,不是 y 本身的值。Stack frame 只記錄局部變數的當前值,所以 calc(x=10, y=20) 是沒問題的。
🎥 實機教學影片(VS 和 GDB)
- Visual Studio Call Stack 教學影片示範:這支影片清楚解說如何開啟 Call Stack 視窗、切換 frame、查看參數與呼叫流程。推薦給初學者快速上手
- Visual Studio Mac 版 Call Stack 教學:步驟相同,適合 macOS 開發者
🛠️ GDB Backtrace 詳解
-
啟動 GDB 並設定斷點
Terminal window gcc -g crash.c -o crashgdb ./crash(gdb) break main(gdb) run -
觸發錯誤(例如 null pointer) 程式崩潰後 GDB 顯示類似資訊:
Program received signal SIGSEGV, Segmentation fault.0x400ac1 in crash() at crash.c:10 -
列出 Call Stack 使用
bt
(backtrace):Terminal window (gdb) bt#0 crash(int* p=0x0) at crash.c:10#1 0x400b27 in main() at crash.c:20你就能清楚看到錯誤在
crash()
,是由main()
呼叫而來。 -
切換 Frame 探查變數
Terminal window (gdb) frame 1 # 切回 main()(gdb) info args # 查看參數(gdb) info locals # 查看區域變數 -
逐步追蹤與繼續
up
/down
:切換呼叫堆疊層級step
/next
:逐行或進入函式finish
:執行到當前函式結束再停止
🧱 Visual Studio Call Stack 操作指南
- 開啟 Call Stack 視窗
Debug → Windows → Call Stack
或按下Ctrl+Alt+C
(Microsoft Learn) - 切換 Frame 追蹤邏輯流程
點擊視窗中的任何 frame,可查看當下變數與返回位置,黃色箭頭顯示目前執行位置,綠箭頭表示當前選取 frame - 設定條件斷點與快速跳轉
- 條件斷點:右鍵斷點 → 設條件
- Run to Cursor:右鍵某 frame 或程式行,立即跳到該處 (Microsoft Learn)
- Code Map(僅限 VS Enterprise)
自動將呼叫流程視覺化成圖,有助理解程式架構 (Microsoft Learn)