從 .c 到可執行檔,GCC 經歷四個截然不同的階段。理解每階段產物與轉換規則,能有效診斷編譯錯誤、連結錯誤與執行時崩潰。本文以 hello.c 為例,逐階段實測並解析中間檔案內容。
| 階段 | 工具 | 輸入 | 輸出 | 核心任務 |
|---|---|---|---|---|
| 預處理 | cpp | .c | .i | 展開 #include、#define、條件編譯、移除註解 |
| 編譯 | cc1 | .i | .s | 語法/語義分析、最佳化、產生組合語言 |
| 組譯 | as | .s | .o | 組語對應機器碼、產生可重定位目標檔 |
| 連結 | ld | .o + .a/.so | 執行檔 | 符號解析、地址重定位、庫整合 |
// hello.c
#include <stdio.h>
#define N 32
int a[N];
int main(void) {
printf("Hello World\n");
return 0;
}
gcc -E hello.c -o hello.i產出 hello.i (約 800+ 行);關鍵變化:
#include <stdio.h> → 完整展開 stdio.h 及其遞迴包含的標頭檔 (型別定義、結構體、函數宣告)#define N 32 → 所有 N 替換為 32,int a[N] 變為 int a[32]#line 指令供後續錯誤報告對應原始行號預處理不進行語法檢查,宏替換純文字展開,錯誤的宏用法會傳遞給編譯器報錯。
gcc -S hello.i -o hello.s產出 hello.s (x86-64 AT&T 語法組語);關鍵段落:
.section .rodata
.LC0:
.string "Hello World" // 字串常數放唯讀資料段
.text
.globl main
main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp // 分配堆疊框架
leaq .LC0(%rip), %rdi // 參數 1: 字串位址 (RIP 相對)
call puts@PLT // 呼叫 puts (比 printf 輕量)
movl $0, %eax // 回傳 0
leave
ret
.rodata:唯讀資料段.text:程式碼段RDI, RSI, RDX, RCX, R8, R9)call puts@PLT:經由 Procedure Linkage Table 動態連結gcc -c hello.s -o hello.o產出 hello.o (ELF 可重定位目標檔);file hello.o 顯示:
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
.text、.rodata、.symtab、.rel.text (重定位表) 等段main、puts (未定義) 等資訊供連結器使用objdump -d hello.o 可反組譯檢視機器碼對應組語gcc hello.o -o hello產出 hello (ELF 可執行檔);file hello 顯示:
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
puts 解析至 libc.so.6call puts@PLT 等位址.o 的同名段合併 (如 .text、.data、.bss)libc、ld-linux;-static 可靜態連結 (體積大幅增加)| 特性 | 靜態連結 -static | 動態連結 (預設) |
|---|---|---|
| 可執行檔大小 | 大 (含庫程式碼) | 小 (僅符號參考) |
| 啟動速度 | 快 (無動態載入) | 稍慢 (需動態連結器) |
| 記憶體共享 | 每進程獨佔 | 多進程共享庫代碼頁 |
| 部署相依性 | 無 (自足) | 需目標系統有相容庫 |
| 安全性更新 | 需重新編譯 | 升級共享庫即生效 |
fatal error: stdio.h: No such file or directory (Include 路徑錯誤)error: expected ';' before '}' token (語法錯誤)、warning: implicit declaration of function (缺標頭檔)undefined reference to 'func' (缺庫或 .o)、multiple definition of 'var' (重複定義)gcc -E -dM hello.c # 列出所有預定義宏
gcc -S -fverbose-asm hello.c # 組語加註釋
objdump -h hello.o # 區段表
objdump -t hello.o # 符號表
readelf -l hello # 程式頭 (載入資訊)
ldd hello # 動態庫相依
nm -C hello.o # 解碼符號名
掌握四階段邊界與產物,除錯時能精準定位:.i 看宏展開、.s 看編譯器最佳化、.o 看符號與重定位、最終執行檔看動態相依。善用 -save-temps 保留所有中間檔,配合 objdump/readelf/nm 深入剖析,是 C/C++ 系統級開發必備技能。