1252 字
6 分鐘
🧠 函式堆疊回溯|Debug|四周目

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 Studiogdb,你可以看到類似這樣的堆疊回溯(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 詳解#

  1. 啟動 GDB 並設定斷點

    Terminal window
    gcc -g crash.c -o crash
    gdb ./crash
    (gdb) break main
    (gdb) run
  2. 觸發錯誤(例如 null pointer) 程式崩潰後 GDB 顯示類似資訊:

    Program received signal SIGSEGV, Segmentation fault.
    0x400ac1 in crash() at crash.c:10
  3. 列出 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() 呼叫而來。

  4. 切換 Frame 探查變數

    Terminal window
    (gdb) frame 1 # 切回 main()
    (gdb) info args # 查看參數
    (gdb) info locals # 查看區域變數
  5. 逐步追蹤與繼續

    • up / down:切換呼叫堆疊層級
    • step / next:逐行或進入函式
    • finish:執行到當前函式結束再停止

🧱 Visual Studio Call Stack 操作指南#

  1. 開啟 Call Stack 視窗
    Debug → Windows → Call Stack 或按下 Ctrl+Alt+C (Microsoft Learn)
  2. 切換 Frame 追蹤邏輯流程
    點擊視窗中的任何 frame,可查看當下變數與返回位置,黃色箭頭顯示目前執行位置,綠箭頭表示當前選取 frame
  3. 設定條件斷點與快速跳轉
    • 條件斷點:右鍵斷點 → 設條件
    • Run to Cursor:右鍵某 frame 或程式行,立即跳到該處 (Microsoft Learn)
  4. Code Map(僅限 VS Enterprise)
    自動將呼叫流程視覺化成圖,有助理解程式架構 (Microsoft Learn)

好難,還是聽歌好了!#

🧠 函式堆疊回溯|Debug|四周目
https://illumi.love/posts/指南向/函式堆疊回溯debug/
作者
Illumi糖糖
發布於
2025-05-08
許可協議
🔒CC BY-NC-ND 4.0