C 語言程式設計教學:以 GCC (或 Clang) 編譯 C 程式

PUBLISHED ON MAY 30, 2018 — PROGRAMMING
FacebookTwitter LinkedIn LINE Skype EverNote GMail Email Email

    本文會選 GCC 而非其他 C 編譯器是因為 GCC 在 GNU/Linux 等類 Unix 系統上具有代表性。如果讀者使用 Clang,有一部分參數相容於 GCC,仍然可以參考;而且 Clang 的錯誤訊息比 GCC 友善,倒也不失為一個好的替代方案。

    Visual C++ 的命令列工具為 cl.exe ,但會從終端機而非 Visual Studio 用 cl.exe 的使用者相對較少,相關資料反而難找,而且 cl.exe 僅限 Windows 系統可用,通用性較差。筆者於後文中另外撰寫一篇使用 cl.exe 編譯 C 程式的教學,有需要的讀者可自行參考。

    對於初學者,先記住以下「公式」即可:

    $ gcc -o hello hello.c
    $ ./hello
    Hello World

    本文的目的是整理一些常用的 GCC 用法,初學者覺得難以吸收的話可以先跳過沒關係。

    檢查 GCC 版本

    參考以下指令:

    $ 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 可以在編譯出來的程式中加上除錯相關的資訊,最後要發布程式時可以關掉這項參數再編譯一次。

    剖析訊息

    加入 -pg 可在編譯時加入 gprof 可用的訊息,可和 -g 併用。

    最佳化

    常見的套餐有以下選項:

    • -O0 (關閉最佳化)
    • -O1
    • -O2
    • -O3
    • -Os (空間最佳化)

    除此之外,還有一些細部的選項可以把玩,一開始學習時不太需要耗費過多時間在這裡。

    多檔案編譯

    參考以下指令:

    $ gcc -Wall -g -o program main.c file_a.c file_b.c file_c.c

    相關的標頭檔 (header) 要預先撰寫,於後文會再說明。

    指定 C 標準版本

    參考以下指令:

    $ gcc -Wall -g -std=c99 -o program main.c

    GCC 中常見的 C 語言標準:

    • c89c90-ansi:即 ANSI C
    • c99
    • c11
    • c17c18:不要和 C++17 搞混,這是一個 C11 的修正版
    • 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 something.o something.c
    $ ar rcs libsomething.a something.o

    編譯動態連結函式庫可參考以下指令:

    $ gcc -fPIC -c -o something.o something.c
    $ gcc -shared -o libsomething.so something.o

    小結

    本文所述大概不到 GCC 中 3% 的特性,但一些冷門的參數其實也很少用,需要時再去查詢即可。如果每次都要手動輸入編譯指令其實蠻辛苦的,我們會再後文介紹 make(1),這是一個流程自動化軟體,可減少手動輸入指令的負擔。