前言
本文會選 GCC 而非其他 C 編譯器是因為 GCC 在 GNU/Linux 等類 Unix 系統上具有代表性。如果讀者使用 Clang,因 Clang 參數刻意相容於 GCC,仍然可以參考本文來學習 Clang;而且 Clang 的錯誤訊息比 GCC 友善,倒也不失為一個學習 C 語言的替代工具。
基本的使用方式
對於初學者來說,先記住以下流程即可:
$ gcc -o hello hello.c
$ ./hello
Hello World
本文的目的是整理一些常見的 GCC (或 Clang) 的使用方式,初學者覺得難以吸收或用不到的話可以先跳過沒關係。
檢查 GCC (或 Clang) 的版本
參考以下指令:
$ gcc --version
gcc (Ubuntu 4.8.4-2ubuntu1~14.04.4) 4.8.4
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
在線上討論區討論問題時,可以貼上自己所用的 C 編譯器的版本,偶爾會從中得到一些有用的回應。
開啟警告訊息
-Wall
會開啟許多有用的警告,一開始練習時建議開啟,可以從錯誤訊息中學習。除此之外,也可以開啟 -Wextra
,有更多的錯誤訊息。參考以下指令:
$ gcc -Wall -Wextra -g -o file file.c
-Werror
會將警告訊息轉為錯誤,初期對練習寫 C 程式會有一些幫助。不過,GCC 有些警告訊息其實不會造成實質的影響,筆者會去看 GCC 的警告訊息,但不會開啟這個選項。
-pedantic
對非標準 C 的語法會出現錯誤訊息,若注重程式碼的相容性可開啟此選項。
開啟除錯相關資訊
加入 -g
可以在編譯出來的程式中加上除錯相關的資訊,如果要對編譯出來的執行檔使用 GDB 等除錯器 (debugger) 除錯時就需要在編譯時加入此參數。由於這個參數對程式運行本身沒有幫助,但是會讓程式的體積變大,最後要發布實際要上線的程式時,建議關掉這項參數重新編譯一次。
開啟剖析 (Profiling) 相關資訊
加入 -pg
可在編譯時加入 gprof 程式可用的訊息,可和 -g
併用。由於在程式中加入剖析相關資訊會影響程式運行效能,程式要正式上線時要關掉這個選項重新編譯。
選擇編譯最佳化策略
GCC 有許多和最佳化相關的參數,這些參數相當複雜。為了簡化最佳化的過程,GCC 提供一些預先配置好的「套餐」。常見的選項如下:
-O0
(關閉最佳化)-O1
-O2
-O3
-Os
(空間最佳化)
-02
是保守但可用的最佳化策略,要效能可試著用 -O3
來編譯程式。但 -O3
有時會優化過頭,導致程式發生預期外的錯誤。除非是效能魔人,不建議用 -O3
來編譯程式。
除此之外,還有一些細部的選項可以把玩。一開始學習時不太需要耗費過多時間在這裡。
編譯多個檔案
初學時會把所有的程式碼寫在同一個檔案中,但實務上的程式會拆成多個檔案。可參考以下指令:
$ gcc -Wall -g -o program main.c file_a.c file_b.c file_c.c
相關的標頭檔 (header) 要預先撰寫,否則 GCC 無法自動串連多個檔案。於後文會再說明。
指定 C 標準 (C Standard) 的版本
參考以下指令:
$ gcc -Wall -g -std=c99 -o program main.c
GCC 中常見的 C 語言標準:
c89
或c90
或-ansi
:即 ANSI Cc99
c11
c17
或c18
:不要和 C++17 搞混,這是一個小的 C 標準修正版
除此之外,還可以加上 GNU 特有的 extension:
gnu89
或gnu90
:c90
加上 GNU C extensiongnu99
:c99
加上 GNU C extensiongnu11
:c11
加上 GNU C extensiongnu17
:c17
加上 GNU C extension
一開始建議使用 c90
或 c99
,需要較新的 C 標準才逐漸加入,以維持相容性。GNU C extension 不是 C 標準的語法,除非很確定該專案只會用到 GCC 來編譯,不建議任意地使用。
加入外部函式庫
除了一些內建的函式庫以外,編譯時要加入相關的參數。常見的例子像是 -lm
(數學公式)、-lpthread
(POSIX 多執行緒)、-lrt
(POSIX realtime extension) 等。參考以下指令:
$ gcc -Wall -g -o program file.c -lm
以 -lm
來說,其讀法為 -l
(函式庫) 加上 m
(數學函式庫),其他函式庫同理可知。
有些函式庫不是位於系統內建的位置上,則要另外加上 -I
(標頭檔位置) 和 -L
(函式庫位置)。參考以下指令:
$ gcc -Wall -g -o program file.c -I/path/to/include -L/path/to/lib -lsomething
pkg-config
是一個用來簡化編譯第三方函式庫的小工具,透過這套工具,我們不用手寫 -I
和 -L
等參數。
例如,我們以 pkg-config
自動產生適用於 libpng 的參數 (於 Mac 上測試):
$ pkg-config --libs --cflags libpng
-I/usr/local/Cellar/libpng/1.6.34/include/libpng16 -L/usr/local/Cellar/libpng/1.6.34/lib -lpng16 -lz
編譯時將此段參數插入指令之中即可:
$ gcc -o program file.c `pkg-config --libs --cflags libpng`
編譯函式庫
函式庫是 C (或 C++) 分享程式的方式,分為靜態連結 (static linking) 和動態連結 (dynamic linking) 兩種。前者會將程式直接編入主程式中,後者在執行期時才動態連結。
編譯靜態連結函式庫可參考以下指令:
$ gcc -c -o a.o a.c
...
$ ar rcs libsomething.a a.o ...
在 macOS 上時則改用 libtool
來編譯靜態函式庫:
$ libtool -static -o libsomething.a a.o ...
編譯動態連結函式庫可參考以下指令:
$ gcc -fPIC -c -o a.o a.c
...
$ gcc -shared -o libsomething.so a.o ...
在 macOS 上最好按照 macOS 的慣例,將動態連結函式的副檔名改為 .dylib 。
小結
本文所述大概僅佔 GCC 參數的一小部分,但一些冷門的參數其實也很少用,需要時再去查詢即可。如果每次都要手動輸入編譯指令其實蠻辛苦的,我們在這裡介紹 GNU Make,這是一個自動化編譯軟體,可減少手動輸入指令的負擔。