GCC 是 GNU Compiler Collection 的簡稱,GCC 原本稱為 GNU C Compiler,隨著時代演進,陸續支援 Fortran 、 Pascal 、 Objective-C 、 Java 、 Ada 、 Go 等程式語言,才改稱 GNU Compiler Collection。
本篇文章只會探討 C 語言的部分,不會對其他程式語言多做介紹。
關於 GNU 計畫,可參考未來幾天會發出的 UNIX、BSD 與 Linux 的愛恨情仇一文。
要將 C 語言編譯成可執行檔,需要經過多個流程:
預處理
將 include 、 macro 擴展成真正的內容,經過這一步驟後,檔案會變大許多。
使用 GCC 進行預處理的方式如下:
gcc -E -I./src main.c -o main.i
-E
會讓 gcc 僅對 c 程式做預處理-I
用來指定自定義標頭檔的優先搜尋目錄,若你定義的標頭檔與程式碼同目錄,就可以忽略該參數。-o
指定輸出檔案的檔名與格式編譯
將預處理過的程式碼編譯成組合語言,而組合語言又會因為不同的處理器架構出現差異,如果要在 x86 平台編譯出 ARM 平台能執行的程式碼,就會需要做交叉編譯。
使用 GCC 進行編譯的方法:
gcc -S -I./src main.c -o main.s
-S
僅輸出 C 程式對應的組合語言組譯
經過編譯後,我們會得到組合語言檔案,也就是 *.s
檔案。不過,若要讓電腦能夠執行我們的程式碼,我們仍需要將其轉換成機器碼。
gcc -c main.s -o main.o
-c
僅編譯 C 程式,但不連結,即輸出對應的目標碼連結
連結會將多個目標文件或是 library 連結成可執行檔。
gcc -o main main.o
得到可執行檔後,可以在 shell command 輸入以下命令執行程式:
./main
如果有多個機器碼最後會被連結成一個可執行檔案,也可以這麼做:
gcc -o main a.o b.o c.o
這樣一來,如果開發者更動了其中一個檔案,我們只需對有更動的檔案進行編譯再連結起來。可以大幅節省編譯的時間。
除了最基本的編譯,當然還要學習其他 GCC 內建的強大功能!
GCC 可以針對不同的處理器架構對程式進行最佳化:
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
若我們需要使用 GDB 進行除錯,在編譯之前,我們需要添加 -g
參數告知 GCC 要盡可能的提供資訊給 GDB :
gcc -Wall -g -o main main.c
我們可以使用 -v
參數啟用 verbose mode,獲得詳細的編譯訊息:
gcc -v main.c
假設開發者在程式中埋了一段程式碼,該程式碼只被用於除錯:
#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
只要函式庫的存放路徑為:
都可以直接使用 -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 的最後一項。