本篇介紹如何在 Linux 系統上使用 GCC 編譯器,將寫好的 C 與 C++ 程式碼編譯成執行檔。
Linux 系統上最常見的 C/C++ 編譯器就是 GCC,它是一個開放原始碼的免費編譯器,幾乎任何的 Linux 系統上都有這個編譯器可用,以下介紹 GCC 的基本用法以及範例。
假設我們寫好一個 C 程式,常程式碼儲存在 hello.c
這個檔案中,而其內容如下:
// hello.c #include <stdio.h> int main() { printf("Hello, world!\n"); return 0; }
若要將 C 的原始碼編譯成執行檔,可以執行 gcc
並指定 C 語言的原始碼檔案:
# 編譯 C 程式
gcc hello.c
GCC 預設會執行編譯與連結,直接產生一個可以執行的程式,輸出至 a.out
這個檔案,完成編譯之後,即可執行這個程式:
# 執行編譯好的程式
./a.out
Hello, world!
若要指定輸出的執行檔名稱,可以加上 -o
參數,並指定輸出的檔案名稱:
# 編譯 C 程式,指定輸出檔名 gcc -o hello hello.c # 執行編譯好的程式 ./hello
假設我們寫好一個 C++ 的程式儲存在 hello.cpp
,內容如下:
// hello.cpp #include <iostream> using namespace std; int main() { cout << "Hello, world!" << endl; return 0; }
若要編譯 C++ 的程式,可以使用 g++
這個編譯程式,並指定 C++ 的原始碼檔案:
# 編譯 C++ 程式
g++ hello.cpp
g++
跟 gcc
非常類似,預設的輸出也是 a.out
這個檔案:
# 執行編譯好的程式
./a.out
Hello, world!
若要指定輸出的執行檔名稱,可以加上 -o
參數,並指定輸出的檔案名稱:
# 編譯 C++ 程式,指定輸出檔名 g++ -o hello hello.cpp # 執行編譯好的程式 ./hello
GCC 可用的參數不勝枚舉,這裡我們僅介紹一些初學者常用的參數用法,以及實際的應用範例。
假設我們寫了一個 C 語言的程式如下:
// bug.c #include <stdio.h> int main() { int v; // 未初始化 printf("v = %d\n", v); return 0; }
其中我們宣告了一個變數 v
,但是卻在沒有定義的狀況下直接使用它,向這樣的程式通常都是有問題的,但是按照一般的編譯方式來說,編譯器並不會顯示錯誤,如果在開發過程想要讓編譯器協助偵測這類的問題,可以加上 -Wall
參數,讓編譯器顯示所有的警告訊息。
而通常在開發與除錯的階段,都會使用 gdb
來除錯,所以也會加上 -g
參數(如果沒加的話,就無法用 gdb
除錯)。
所以在開發階段,會很常用到像這樣的指令來編譯程式:
# 顯示警告訊息、加入除錯資訊 g++ -Wall -g -o hello hello.cpp
在開發中大型的程式時,開發者通常會使用一些巨集來處理各種的問題,例如除錯時會需要輸出詳細的訊息,但正式版的程式又不需要,這時候就可以這樣寫:
// macro.c #include <stdio.h> int main() { printf("Hello, world!\n"); #ifdef DEBUG printf("DEBUG is defined.\n"); #endif return 0; }
這段程式中,我們用 #ifdef
判斷 DEBUG
這個變數是否有被定義,若有被定義的話,就加入輸出除錯的程式碼,這樣一來我們就可以透過 DEBUG
的定義,來決定是否要產生有除錯訊息的執行檔。
若要定義 DEBUG
這個變數,可將定義直接加在程式碼中:
// 定義 DEBUG #define DEBUG
也可以直接透過 GCC 的 -D
參數來定義:
# 定義 DEBUG gcc -DDEBUG macro.c
兩種方式的效果相同,不過使用 GCC 的參數來定義這種時常需要調整的變數,通常會方便很多。
GCC 預設會將 C 的原始碼編譯並連結,產生執行檔,若想讓編譯器只進行編譯、不要連結,可加上 -c
參數,這樣就會建立一個 object 檔:
# 僅編譯、不連結,建立 obj 檔案 gcc -c hello.c
執行這行之後,就會產生一個 hello.o
檔案,後續若要進行連結,就可以使用這個 object 檔:
# 連結產生執行檔 gcc -o hello hello.o
通常在大型的專案中,都會將編譯與連結兩個動作拆開,以下是一個典型的例子:
# 編譯個別 C 檔案 gcc -c a.c gcc -c b.c gcc -c c.c # 連結 gcc -o myapp a.o b.o c.o
這樣作除了可讓程式碼方便管理之外,也可以加快編譯的速度,假設我們在開發過程中,更改了 b.c
的內容,在重新編譯時就只要編譯 b.c
,然後即可進連結,省去重新編譯 a.c
與 c.c
的時間。
GCC 可以根據 CPU 的架構,進行最佳化處理,編譯出效能更好的執行檔,可用的選項有 -O
(跟 -O1
相同)、-O2
與 -O3
,數字越高代表最佳化的程度越高,許多專案在編譯正式版的時候,都會使用 -O2
進行最佳化。
# 進行最佳化 gcc -O2 -o hello hello.c
在編譯程式時,編譯器需要許多標頭檔(*.h
檔案)來編譯原始碼,而連結的時候則會需要一些函式庫(*.a
、*.so
等)才能進行連結,但是編譯器只會自動引入一些系統預設的檔案,在大型專案中開發者會需要指定許多額外的標頭檔與函式庫位置,這樣才能讓編譯器順利編譯與連結。
編譯器會在 include 路徑中,搜尋 C 程式碼中以 #include
所引入的標頭檔,如果需要指定額外的搜尋路徑,可以使用 -I
參數增加搜尋路徑,假設我們有一些標頭檔放在 /home/gtwang/include
目錄下,而要新增這個路徑就可以這樣寫:
# 新增標頭檔搜尋路徑 gcc -I/home/gtwang/include -o hello hello.c
在連結時期所需要函式庫名稱與路徑,可以透過 GCC 的 -l
與 -L
參數來指定。在 Linux 中假設有一個 libsum.a
函式庫放在 /home/gtwang/lib
目錄下,若要將其納入連結,則可以這樣寫:
# 納入指定的函式庫 gcc -lsum -L/home/gtwang/lib -o hello hello.c
關於 C/C++ 的函數庫製作方式,可以參考建立 C/C++ 靜態、共享與動態載入函式庫教學文章。
參考資料:Study-Area、NTU