1. 程式人生 > 其它 >GCC編譯過程和原理淺析

GCC編譯過程和原理淺析

技術標籤:GCC編譯器入門編譯器javalinuxc++makefile

什麼是GCC

  • GCC(GNU C Compiler)編譯器的作者是Richard Stallman,也是GNU專案的奠基者。
  • GCC是GNU Compiler Collection的縮寫。最初是作為C語言的編譯器,現在已經支援多種語言了,如C、C++、Java、Pascal、Ada、COBOL語言等。
  • GCC支援多種硬體平臺,甚至對Don Knuth設計的MMIX這類不常見的計算機都提供了完善的支援。

GCC的主要特徵

  • GCC是一個可移植的編譯器,支援多種硬體平臺。
  • GCC不僅僅是本地編譯器,它還能跨平臺交叉編譯。
  • GCC有多種語言前段,用於解析不同的語言。
  • GCC是按模組化設計的,可以加入新的語言和新CPU架構的支援。
  • GCC是自由軟體。

GCC編譯程式的過程

編譯過程
  1. 預處理(Pre-Processing):主要包括巨集定義,檔案包含,條件編譯三部分。預處理過程讀入原始碼,檢查包含預處理指令的語句和巨集定義,並對其進行響應和替換。預處理過程還會刪除程式中的註釋和多餘空白字元。最後會生成 .i 檔案。
  2. 編譯器(Compiling):編譯器會將預處理完的 .i 檔案進行一些列的語法分析,並優化後生成對應的彙編程式碼。會生成 .s 檔案。
  3. 彙編器(Assembling):彙編器會將編譯器生成的 .s 彙編程式彙編為機器語言或指令,也就是可以機器可以執行的二進位制程式。會生成 .o 檔案。
  4. 連結器(Linking):連結器會來連結程式執行的所需要的目標檔案,以及依賴的庫檔案,最後生成可執行檔案,以二進位制形式儲存在磁碟中。

GCC的編譯過程

GCC常用選項

  • -o:生成目標( .i 、 .s 、 .o 、可執行檔案等)
  • -c:通知 gcc 取消連結步驟,即編譯原始碼並在最後生成目標檔案。
  • -E:只執行 C 預編譯器
  • -S:告訴編譯器產生組合語言檔案後停止編譯,產生的組合語言副檔名為 .s
  • -Wall:使 gcc 對原始檔的程式碼有問題的地方發出警告
    • Idir:將dir目錄加入搜尋標頭檔案的目錄路徑
  • -Ldir:將dir目錄加入搜尋庫的目錄路徑
  • -llib:連線lib庫
  • -g:在目標檔案中嵌入除錯資訊,以便gdb之類的除錯程式除錯

預處理過程

我們以 hello.c 程式為例:

#include <stdio.h>

#define HELLOWORLD ("hello world\n")

int main(void)
{
    printf(HELLOWORLD); 
    return 0;
}

使用gcc -E hello.c -o hello.i命令,將 hello.c 檔案預處理並且生成 hello.i 目標檔案。

之前說道,預處理會將標頭檔案包含進來並且會將巨集定義進行替換,因此替換後的 hello.i 檔案如下:

# 1 "hello.c"
# 1 "<built-in>"
# 1 "<命令列>"
# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 28 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 361 "/usr/include/features.h" 3 4
# 1 "/usr/include/sys/cdefs.h" 1 3 4
# 365 "/usr/include/sys/cdefs.h" 3 4
# 1 "/usr/include/bits/wordsize.h" 1 3 4
# 366 "/usr/include/sys/cdefs.h" 2 3 4
# 362 "/usr/include/features.h" 2 3 4
# 385 "/usr/include/features.h" 3 4
# 1 "/usr/include/gnu/stubs.h" 1 3 4

......

extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__));
# 938 "/usr/include/stdio.h" 3 4

# 2 "hello.c" 2


int main()
{
printf(("hello world!\n"));
return 0;
}

可以看到將 stdio.h 檔案包含進來,並且原封不動的將 HELLOWORLD 巨集進行了替換。

編譯過程

使用gcc -S hello.i -o hello.s,將生成的hello.i檔案編譯為彙編程式hello.s。

.file   "hello.c"
    .section    .rodata
.LC0:
    .string "hello world!"
    .text
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    movl    $.LC0, (%esp)
    call    puts
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-23)"
    .section    .note.GNU-stack,"",@progbits

可以看到hello.s檔案中全部都是彙編指令,說明已經生成成功了。

彙編過程

彙編就是要將hello.s檔案中的彙編指令全部轉換為二進位制的機器指令。
執行gcc -c hello.s -o hello.o命令。而生成的hello.o檔案是二進位制檔案,我們用od -b hello.o命令看一下該二進位制檔案的八進位制表示。

0000000 177 105 114 106 001 001 001 000 000 000 000 000 000 000 000 000
0000020 002 000 003 000 001 000 000 000 
......

連結過程

連結hello.o程式執行的所需要的目標檔案,以及依賴的庫檔案,最後生成可執行檔案。
執行gcc hello.o -o hello不需要選項,生成hello二進位制的可執行檔案。同樣可以使用od命令來檢視。執行hello檔案:./hello

hello world

以上編譯過程的分步驟進行,還可以直接執行gcc hello.c -o hello直接生成可執行檔案。
至此,使用gcc編譯程式的過程就介紹完畢。

待更新...