前言
GCC,全稱The GNU Compiler Collection,包含了 C, C++, Objective-C, Fortran, Ada, Go等語言的編譯器前端以及這些語言所依賴的一些庫文件。雖然現在的IDE基本可以搞定很多編譯的事情,但是很多時候,b比如我們看源碼的時候、自己編寫一些Python C++擴展模塊的時候等,使用IDE可能會比較繁瑣,這時候就需要手動編譯,需要看懂或者會編寫編譯腳本,這樣,我們想在別人的基礎上作些改動纔有可能。因此有必要了解一下GCC。
需要說明的是,這裏的GCC和你在命令行調用的gcc並不是一個東西。做個簡單但很可能不完全正確的說明,GCC表示一套編譯器的集合,而gcc,最開始表示GUN C Compiler,用於編譯C,後來隨着越來越多東西的加入,變成了GCC。現在gcc和g++只不過是GNU Compiler Collection的驅動程序。他們之間最大的區別可能就是他們鏈接的時候所使用的鏈接庫不同。簡單點記就是用gcc編譯C,g++編譯C++,當然,你也可以-x選項去使用gcc編譯其他語言,例如使用gcc -xc++
去編譯C++。
參數類型分類
- 控制輸出結果,如
-c -S -E -o file -x language
等; - 控制當前編譯源碼所需遵循的語言標準,如
-ansi -std=standard -fgnu89-inline
等; - 控制警告信息,如
-Wall
等; - 控制調試,如
-g -glevel*
等; - 控制優化,如
-O -O0 -O1 -O2 -O3 -Os -Ofast -Og
等; - 控制預處理;
- 控制彙編;
- 控制鏈接,如
-shared -shared-libgcc -symbolic -llibrary
; - 控制代碼生成;
- 控制編譯過程所需要搜索的目錄,如
-Bprefix -Idir -I-
; - 硬件依賴;
- 開發者相關。
這裏只是分類簡單介紹一些常用的選項。更多更詳細的信息參閱參考文檔的內容。
控制輸出結果
我們知道,編譯過程可以分爲預處理、編譯、彙編、鏈接四個階段。這四個階段按順序進行,並且每個階段都會產出用於下一個階段的中間產物。由於編譯並不是一步到位,因此我們可以通過參數告訴編譯進行到哪一步,例如,我們只想編譯得到目標文件,而不想直接鏈接得到可執行文件,那麼我們可以在命令行中使用-c
參數來告訴編譯器不進行鏈接操作。
控制編譯輸出的結果主要通過下面四個參數:
-E
:只進行預處理-S
:只進行到編譯階段,不進行彙編和鏈接操作-c
:不進行鏈接操作,只生成目標文件-o
:指定輸出文件
控制源碼所遵循的標準
每一種語言都有自己獨特的語法語義,隨着語言的不斷髮展,每種語言的語法也在變化着。比如C++,相關的標準有C++98,C++11等,由於GCC編譯器可以接受多個標準,並且有其默認使用標準,使用不同的標準去編譯一份源碼可能得到的結果不盡相同,所以有很多時候,我們需要指定當前源碼所遵循的標準。例如,我們需要使用C++11標準,我們可以通過-std=c++11
來指定。
控制警告信息
有個笑話,說有個人走到懸崖邊看到一個牌子寫着:
warning: 前面是懸崖,不要再往前走了。
可是這個人還是摔死了,因爲他是程序員,總是習慣性的忽略所有warning。
在編譯過程中,很可能會出現一些警告信息,雖然有時候這些警告並不影響結果,但是警告的出現通常預示着代碼有可能存在風險或者潛藏着錯誤。因此我們可以選擇讓編譯器報告或者步報告某些特定警告信息。一般指定顯示特定警告信息的參數都以-W
開頭,例如,當需要警告使用了隱式聲明時,可以用-Wimplicit
;與之相對,當希望編譯器忽略某一類型的警告信息時,一般用-Wno
開頭的參數通知編譯器,例如使用-Wno-implicit
會關掉所有使用了隱式聲明的警告。
有些參數,本身並不是控制顯示特定警告信息,而是控制其他參數,例如-Wall
,這並不是“牆壁”的意思,而是一個有多個控制警告信息的參數的集合,使用了-Wall
就相當於同時使用了多個其他-W
開頭的參數,例如使用-Wall
就相當於同時使用了-Waddress -Warray-bounds=1 -Wbool-compare
以及上面提到的-Wimplicit
等許多參數。
控制調試
當需要調試的時候,一般需要告訴編譯器去生成一些額外的信息用於調試,很多時候,只需要指定-g
就夠了。
當然,用於控制調試的參數遠不止於此。與控制警告信息的參數類似,控制調試的參數很多都是以-g
開頭,例如,當需要指定所產生的調試信息的格式時,可以使用-gdwarf、-gstabs、-gxcoff
等,它們分別表示調試信息的格式爲DWARF、stabs、XCOFF。
控制優化
當不指定優化策略的時候,編譯器的目標將是最小化編譯過程中的開銷,這個開銷一般只時間。
但是很多時候,我們需要在程序運行效率或者程序體積方面作出優化。當然,有得必有失,優化之後我們也會失去程序的可調試性或者在時間上多花時間。我們可以使用-O
開頭的參數告訴編譯器我們想要優化的內容。
- -O0:基本不做優化;
- -O\-O1:嘗試優化程序體積和執行時間;
- -O2:比-O1作出更進一步的優化;
- -O3:比-O3更進一步;
- -Os:優化程序體積
- -Og:優化調試體驗
控制鏈接
編譯的最後階段便是鏈接。就相當於製作一臺電腦的零部件都已經正確加工完畢擺放在那,但是距離這臺電腦能跑起來還差正確組裝。鏈接就是負責把所有目標文件組裝成一個可執行文件或者一個庫文件。
在鏈接階段,有很多參數可以控制鏈接過程,比如,使用什麼鏈接器去做鏈接、傳遞什麼參數給選定的鏈接器、鏈接生成物的作用、程序的入口在那兒、鏈接此程序還需要那些庫輔助等。
鏈接階段主要用到的參數有以下幾個:
- -fuse-ld:選擇使用的鏈接器,可選項有
-fuse-ld=bfd -fuse-ld=gold -fuse-ld=lld
分別表示使用bfd鏈接器、gold鏈接器、LLVM lld鏈接器; - -shared\-static:告訴編譯器鏈接生成物是一個庫,而不是可執行程序。其中
-shared
表示生成的是動態鏈接庫,例如Linux中的.so庫。由於不是所有系統都支持動態鏈接庫,因此,如果使用了-shared
參數,必須同時提供-fpic 或 -fPIC
參數; - -l library\-llibrary:提供鏈接過程中所依賴的其同庫文件。需要注意的是
-l library
這種用法,只有在Linux這類系統中有效。另外,庫文件搜索所使用的目錄包括一些標準目錄以及-L
參數所指定的目錄; - -e entry\-entry=entry:指定程序入口;
- -Wl,option:指定傳遞給選擇的鏈接器的參數,如果option包含多個參數,用逗號作爲分隔符,例如
-Wl,-Map, output.map
表示 把-Map=output.map
傳遞給了鏈接器;
控制搜索目錄
編譯過程中,有時候僅僅是標準目錄是不夠的,很多時候需要的一些頭文件、三方庫、編譯器的部分依賴等並不在標準目錄中,所以需要告訴編譯器,去那裏可以找到所需要的這些文件。
下面介紹一些常見的參數:
- -Bprefix:指定編譯器自身所需要的可知性文件、數據文件、包含文件等;
- -Ldir:指定鏈接階段使用
-l
指定的庫文件的搜索目錄; - -I dir:指定頭文件搜索目錄;
- -I-\-iquote:指定引用類型的頭文件(形如
#include "file.h"
)的搜索路徑; - -isystem\-idirafter:添加的目錄類似於標準目錄。
既然-I\-iquote\-isystem\-idirafter
等都是指定頭文件搜索路徑,那麼編譯器先搜索那裏再到那裏呢?其搜索順序如下:
- 當前目錄;
- 搜索
-iquote
指定的目錄; - 搜索
-I
指定的目錄; - 搜索
-isystem
指定的目錄; - 搜索標準目錄;
- 搜索
-idirafter
指定的目錄。
確定了搜索順序,我們就能做一些選擇,例如有和標準目錄中的頭文件同名的頭文件,我們可以選擇放在標準目錄前或後,這樣就能選擇是否覆蓋標準目錄的頭文件。需要說明的是,第一、第二步只適用於引用類型頭文件。
高級功能
類似於一些硬件相關、開發者相關的參數,這裏就不做介紹了,有興趣的可以參閱這裏。
舉個栗子
上面簡單介紹了一些GCC的命令,當我們需要編譯的時候,只需要分別在這幾類命令中選出一些我們需要的,就可以編譯了。 比如,我寫了個Python的C++擴展模塊,我現在需要編譯:
-
首先,確定使用g++作爲驅動;
-
第二部,我想我的程序體積和運行時間都比較好,所以我選擇
-O3
優化; -
第三步,關於警告信息,我就使用
-Wall
去打開常用的開關; -
第四步,這是個動態鏈接庫,所以我指定
-shared
以及-fPIC
; -
第五步,通過
-o
參數自己定一個庫文件名; -
第六步,通過
-I
來指定下編譯過程中需要的一些頭文件所在的目錄; -
其他的,類似預處理、調試及其他相關的參數,我這裏並沒有特殊要求,所以就不使用了。 那麼我的編譯命令就是下面這樣子:
g++ -O3 -Wall -shared -std=c++11 -I/home/example/playground/pybind11/include my_module.c -o example.so -I/usr/include/python3.5m -I//home/example/playground/pybind11/include -fPIC
總結
這裏只是個人理解的一個簡單介紹。面對看似複雜的事情,如果能找到方法簡化,定能事半功倍。
References
https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/index.html#SEC_Contents
本文首發於個人微信公衆號TensorBoy。如果你覺得內容還不錯,歡迎分享並關注我的微信公衆號TensorBoy,掃描下方二維碼獲取更多精彩原創內容!