首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

GCC 編譯流程深度解析:預處理、編譯、組譯、連結四階段完整圖解

     編輯:關於C

從 .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:程式碼段
  • 呼叫慣例:System V AMD64 ABI (前六整數參數 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.6
  • 重定位:修正 call puts@PLT 等位址
  • 段合併:多個 .o 的同名段合併 (如 .text、.data、.bss)
  • 動態連結:預設動態連結 libc、ld-linux;-static 可靜態連結 (體積大幅增加)

靜態 vs 動態連結對比

特性靜態連結 -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++ 系統級開發必備技能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved