位元詩人 [C 語言] 程式設計教學:如何在終端機中使用 GCC (或 Clang)

C 語言編譯器
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

本文會選 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 語言標準:

  • c89c90-ansi:即 ANSI C
  • c99
  • c11
  • c17c18:不要和 C++17 搞混,這是一個小的 C 標準修正版

除此之外,還可以加上 GNU 特有的 extension:

  • gnu89gnu90c90 加上 GNU C extension
  • gnu99c99 加上 GNU C extension
  • gnu11c11 加上 GNU C extension
  • gnu17c17 加上 GNU C extension

一開始建議使用 c90c99,需要較新的 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,這是一個自動化編譯軟體,可減少手動輸入指令的負擔。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。