3 3 GNU Compiler Collection - ianchen0119/AwesomeCS GitHub Wiki

GCC 是 GNU Compiler Collection 的簡稱,GCC 原本稱為 GNU C Compiler,隨著時代演進,陸續支援 Fortran 、 Pascal 、 Objective-C 、 Java 、 Ada 、 Go 等程式語言,才改稱 GNU Compiler Collection。 本篇文章只會探討 C 語言的部分,不會對其他程式語言多做介紹。

關於 GNU 計畫,可參考未來幾天會發出的 UNIX、BSD 與 Linux 的愛恨情仇一文。

C 語言的編譯流程

要將 C 語言編譯成可執行檔,需要經過多個流程:

  1. 預處理 將 include 、 macro 擴展成真正的內容,經過這一步驟後,檔案會變大許多。 使用 GCC 進行預處理的方式如下:

    gcc -E -I./src main.c -o main.i
    
    • -E 會讓 gcc 僅對 c 程式做預處理
    • -I 用來指定自定義標頭檔的優先搜尋目錄,若你定義的標頭檔與程式碼同目錄,就可以忽略該參數。
    • -o 指定輸出檔案的檔名與格式
  2. 編譯 將預處理過的程式碼編譯成組合語言,而組合語言又會因為不同的處理器架構出現差異,如果要在 x86 平台編譯出 ARM 平台能執行的程式碼,就會需要做交叉編譯。 使用 GCC 進行編譯的方法:

    gcc -S -I./src main.c -o main.s
    
    • -S 僅輸出 C 程式對應的組合語言
  3. 組譯 經過編譯後,我們會得到組合語言檔案,也就是 *.s 檔案。不過,若要讓電腦能夠執行我們的程式碼,我們仍需要將其轉換成機器碼。

    gcc -c main.s -o main.o
    
    • -c 僅編譯 C 程式,但不連結,即輸出對應的目標碼
  4. 連結 連結會將多個目標文件或是 library 連結成可執行檔。

    gcc -o main main.o
    

    得到可執行檔後,可以在 shell command 輸入以下命令執行程式:

    ./main
    

    如果有多個機器碼最後會被連結成一個可執行檔案,也可以這麼做:

    gcc -o main a.o b.o c.o
    

    這樣一來,如果開發者更動了其中一個檔案,我們只需對有更動的檔案進行編譯再連結起來。可以大幅節省編譯的時間。

其他常見參數

除了最基本的編譯,當然還要學習其他 GCC 內建的強大功能!

程式最佳化

GCC 可以針對不同的處理器架構對程式進行最佳化:

  • O0
  • O1
  • O2 (常用)
  • Os
  • O3 (最佳化)
gcc -O2 -o main main.c

顯示錯誤資訊

考慮以下程式碼:

#include <stdio.h>
int main(){
    int n;
    printf("number is %d\n", n);
    return 0;
}

該程式中,n 並沒有被正確初始化,不過 GCC 預設是不會有錯誤提示的,若開發者有需要,可以使用 -Wall 參數:

gcc -Wall -o main main.c

使用 GNU Debugger

若我們需要使用 GDB 進行除錯,在編譯之前,我們需要添加 -g 參數告知 GCC 要盡可能的提供資訊給 GDB :

gcc -Wall -g -o main main.c

verbose mode

我們可以使用 -v 參數啟用 verbose mode,獲得詳細的編譯訊息:

gcc -v main.c

Macro

假設開發者在程式中埋了一段程式碼,該程式碼只被用於除錯:

#include <stdio.h>
int main(){
    int n = 0;
    for(int i =0;i<1000;i++){
        n += i;
        #ifdef DEBUG
        printf("n is %d\n", n)
        #endif
    }
    return 0;
}

我們除了可以在程式碼中加入 #define DEBUG 外,還可以直接對 GCC 下 -D 參數:

gcc -DDEBUG -o main main.c

除了可以用 -D 定義 Macro,GCC 還允許開發者使用 -U 參數取消定義:

gcc -UDEBUG -o main main.c

標頭檔目錄

剛剛已經在預處理的部分提到 -I 的用途。此外,GCC 在編譯程式時只預設引入一部份的標頭檔,如果程式需要使用特別的 Library 進行編譯,可以使用 -l-L 參數:

  • -l 告知 GCC 編譯程式時需要使用這個 Library : 以 POSIX Thread 為例:

    gcc -lpthread -o main.out main.c
    

    需要注意的是,這裡的 Library name 不等於檔案名稱,在 Linux 下的函式庫都要使用 lib 開頭,其中 *.so 是動態連結函式庫,*.a 是靜態連結函式庫。 由此可知,把 File name 的 lib.so 去掉就是 Library name 了。

  • -L 只要庫的存放路徑為:

    • /lib
    • /usr/lib
    • /usr/local/lib

    都可以直接使用 -l 進行連結,換句話說,如果 Library 的位置並非上述的目錄,我們就需要用 -L 告知 GCC Library File 所在的目錄位置:

    gcc -lsum -L/home/gtwang/lib -o main.out main.c
    

查找依賴關係

Makefile 可以讓開發者省略複雜的編譯選項及參數,考慮以下專案結構:

|-- project
    |-- src/
    |   |-- main.c
    |   |-- linked-list.c
    |   |-- linked-list.h
    |   |-- node.c
    |   |-- node.h

並且,在 main.c 中引用了 linked-list.h,當我們使用下面命令,就可以查找出 main.c 的依賴關係:

gcc -MM main.c

輸出:

main.o: main.c linked-list.h

再假設 linked-list.h 中有使用到 node.h,輸出就會是:

main.o: main.c linked-list.h node.h

如果希望將標準庫的依賴關係也列出來,使用 -M 即可:

gcc -M main.c

關於依賴關係,還有相當多種參數可以玩,詳細資訊可以參考 Reference 的最後一項。

Reference

⚠️ **GitHub.com Fallback** ⚠️