位元詩人 [C++] 程式設計教學:在終端機使用 GCC 或 Clang

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

前言

本文的目的,在於列出一些 GCC 常用的情境,供有需要的讀者參考。由於 Clang 在參數上刻恴的向 GCC 靠攏,這些參數對於 Clang 通常也適用。

基本的使用方式

一開始先知道以下指令即可:

$ g++ -o program source.cpp
$ ./program

檢查 GCC 版本

使用 --version 參數可以顯示編譯器的版本號:

$ g++ --version
g++-8 (Homebrew GCC 8.1.0) 8.1.0
Copyright (C) 2018 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.

不同版本的編譯器提供的特性相異。在討論區討論時,有時候編譯器版本可以提示一些有用的訊息。

加入警告訊息

建議在編譯時一律加入 -Wall-Wextra 來開啟一些常見的警告訊息:

$ g++ -Wall -Wextra -o hello hello.cpp

這兩個參數不會開啟所有的警告訊息,但對一般使用已經足夠了。

再加上 -Werror 參數可將警告訊息轉為錯誤訊息。但有時候警告訊息是無害的,不用刻意消除所有警告訊息,只要明確知道警告訊息的來源即可。

加入除錯訊息

在編譯時加上 -g 參數可以在執行檔內加入給 GDB 使用的資訊,有利於除錯。但該參數對實際上線的程式沒幫助。實際要對外發佈程式前,最好先關掉該參數後重新編譯一次程式。

加入剖析訊息

在編譯時加上 -pg 參數可在執行檔內加入給剖析器使用的資訊,有利於找出程式內的瓶頸步驟。但該參數本身會拖慢程式的運行。實際要發佈程式前,最好關掉該參數後重新編譯一次程式。

最佳化

和最佳化相關的參數很多,所以 GCC 提供一些套餐選項。常見的選項如下:

  • -O0:關閉最佳化。預設選項
  • -O1:第一級最佳化
  • -O2:第二級最佳化。較保守的編譯會採用此級
  • -O3:第三級最佳化。偶爾會造成程式不穩定
  • -Os:對空間最佳化。需要考慮執行檔大小時使用

由於最佳化和語法無關,在學習語法的階段,不用耗費過多時間在這裡。

編譯多個檔案

有時候程式碼比較長,會按程式碼的功能拆開到不同檔案上。本節說明編譯多個檔案的方式。

使用單一指令

只用單一指令的方式如下:

$ g++ -o program a.cc b.cc c.cc

要編譯函式庫的話,這種方式就行不通了。還是要會分步編譯的流程。詳見下一小節。

拆解成多個步驟

先將原始碼逐一編譯成目的檔 (object file):

$ g++ -c a.o a.cc
$ g++ -c b.o b.cc
$ g++ -c c.o c.cc

然後再從目的檔編譯出執行檔:

$ g++ -o program a.o b.o c.o

編譯函式庫的步驟和本小節的方式類似。詳見後文。

指定 C++ 標準版本

在編譯時加上 -std= 參數可以指定 C++ 標準的版本。以下是可用的 ISO C++ 版本:

  • c++98c++03:相當於 ANSI C++
  • c++11:現代 C++ 的起始版本
  • c++14
  • c++17
  • c++20 (實驗性質)

虛擬指令如下:

$ g++ -std=c++17 program source.cc

除了 ISO C++ 外,還可以加上 GNU extension。以下是可用的版本:

  • gnu++98gnu++03
  • gnu++11
  • gnu++14
  • gnu++17
  • gnu++20 (實驗性質)

除非專案只用 GCC 編譯,不建議使用 GNU extension。我們的教學文章不會加上這些非標準特性。

加入外部函式庫

除了少數標準函式庫的函式庫外,編譯時要用 -l 參數來指明使用的函式庫。像是 -lm 是 C 數學函式庫,即為 -l 加上 m 組合而成。

如果函式庫所在的位置非系統預設位置,還要用額外的參數來指定函式庫位置。-I 用來指定標頭檔位置,-L 則用來指定二進位檔位置。

編譯函式庫

函式庫是 C++ 用來共享程式的方式。本節介紹編譯函式庫的方式。

編譯靜態函式庫 (Static Library)

先將原始碼編譯成目的檔:

$ g++ -c a.o a.cc
$ g++ -c b.o b.cc
$ g++ -c c.o c.cc

再用 ar(1) 將目的檔編成靜態函式庫:

$ ar rcs libmylib.a a.o b.o c.o

編譯動態函式庫 (Dynamic Library)

先將原始碼編譯成目的檔。注意這時要加上 -fPIC 參數:

$ g++ -fPIC -c a.o a.cc
$ g++ -fPIC -c b.o b.cc
$ g++ -fPIC -c c.o c.cc

再將目的檔編譯成動態函式庫。注意這時要加上 -shared 參數:

$ g++ -shared -o libmylib.so a.o b.o c.o

靜態函式庫和動態函式庫的目的檔不能通用。編譯時要先清掉舊的目的檔,更改參數後再重編程式碼。

關於作者

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

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