帶除錯資訊編譯(compiling with debug info)
當編譯器把你可讀的原始碼變成機器碼時,它通常會丟掉所有只給人看的東西:你的變數名稱、每條指令來自哪一行、你結構的形狀。CPU 不需要這些。但除錯器極其需要——沒有它們,除錯器只能給你看原始的位址與十六進位。帶除錯資訊編譯,意思就是請編譯器也保留一份這些對人友善的對應的旁表,好讓除錯器能把機器翻譯回你的用語。
具體來說,你把 -g 旗標傳給編譯器:「gcc -g main.c」或「clang -g main.c」。這會把除錯資訊(在 Linux 上是一種叫 DWARF 的格式)嵌進可執行檔——一張從每條機器指令對回它的原始檔與行號、從每個儲存位置對回它的變數名稱、以及對每個型別的描述的地圖。有了它,除錯器就能說「你在 main.c 第 42 行,變數 count 裝著 7」,而不是「你在位址 0x401136,0x7fffffffe0a4 處的值是 0x07」。重要的是,-g 並不會拖慢你的程式、也不會改變它計算的結果——它只是加上資訊;執行的程式碼是一樣的。它確實會讓可執行檔變大,你可以日後在出貨時剝除(strip),同時保留一份獨立的符號檔。
為何重要:-g 是讓除錯器、呼叫堆疊追蹤或核心傾印變得可讀、而不是一堵十六進位位址的牆之間的差別,所以它幾乎是一切原始碼層級除錯的入場費。它與最佳化之間有張力,而最佳化是另一個獨立的軸:在 -O2 下編譯器會重排、合併、刪除程式碼,所以即使帶了 -g,除錯器也可能回報「<value optimized out>」或令人困惑地在行間跳。在獵捕臭蟲時常見的折衷是「gcc -g -O0」(有除錯資訊、關閉最佳化),以獲得忠實的除錯體驗,發行時再帶最佳化建構——但要記得,一個只在 -O2 下才出現的臭蟲,在 -O0 下不會重現。
比較看看: gcc main.c -o a 那麼當掉時 gdb 裡只顯示位址。但 gcc -g -O0 main.c -o a 讓 gdb 顯示真正的行號與變數名稱,例如「Segmentation fault in parse (s=0x0) at parse.c:30」。程式行為相同,除錯的可讀性卻天差地別。
-g 在不改變行為的前提下加上原始碼層級的資訊;-O0 讓這些資訊不會被最佳化掉。
除錯資訊(-g)與最佳化(-O)是各自獨立的:-g 不會關閉最佳化,而帶 -g 的最佳化建構對編譯器移除掉的變數仍可能顯示「<optimized out>」。一個只在 -O2 下出現的臭蟲在 -O0 下可能真的消失,而這本身就是最佳化器正在利用未定義行為的強烈暗示。