linux下的c++編譯

1. 關於GCC

目前在linux上,C++的編譯主要依賴於GCC,而GCC實際上並不是一個編譯器,其全稱爲GNU Compiler Collection,即GNU的編譯器集合,可以通過它來編譯C/C++/Object-C等各種語言的源碼。
其中gcc是GCC中的GNU C Compiler(c編譯用),g++即爲GCC中的CNU C++ Compiler(c++編譯用)。
gcc和g++的主要區別可以查看這位博主的文章:GCC與gcc,g++區別

1.1 使用g++編譯cpp文件

材料:裝了HelloWorld的一份cpp代碼
工具:裝了linux和GCC的一臺機器
然後直接調用命令就可以完成編譯了:

g++ -o hello hello.cpp 

其中-o用於指定輸出文件名稱,上面命令中即爲hello,而後的參數爲需要編譯的源碼。(不帶-o參數也可,則輸出文件默認爲a.out)
調用./hello,大夥兒喜聞樂見的HelloWorld就打印出來了。
這是最基礎的一種編譯方式,在實際項目中,因爲源碼文件存在多個,也有需要包含的鏈接庫/頭文件,以及指定生成目標爲動態鏈接庫/靜態鏈接庫等等,所以會需要一些額外的參數,類似於

  1. -E 參數,普通編譯的第一步,輸出源文件的預處理結果(加載頭文件,宏替換,條件編譯等),示例:
g++ -E hello.cpp -o hell.e
  1. -S 參數,普通編譯的第二步,執行代碼中的語法檢查和詞法分析,檢查正確後將其翻譯爲彙編代碼。
g++ -S hello.e -o hello.s
  1. -c 參數,普通編譯的第三步,將彙編代碼轉換成二進制代碼。
g++ -c hello.s -o hello.o
  1. 無編譯類型參數,普通編譯的最後一步,將二進制代碼與庫文件等其他文件鏈接,生成最終目標文件。(注意這四個步驟中,GCC會自動檢查之前步驟是否完成,若之前的步驟未完成,則自動執行對應的操作)
g++ hello.o -o hello
  1. -o 參數,指定當前編譯過程中產生的目標文件名(就像上面的示例,另外文件後綴是隨便寫的,linux有啥要求)。
  2. -I 參數(大寫i),指定包含的頭文件所在目錄,相對路徑和絕對路徑均可,存在多個目錄時多次添加即可。
g++ hello1.cpp hello2.cpp -o hello -I ./../include1 -I ./../include2
  1. -L 參數,指定鏈接的庫文件所在目錄,同上。
  2. -l 參數(小寫L),指定鏈接的庫文件名稱,默認lib前綴,也不需要後綴,如鏈接liblua.a,寫做-llua即可。一般情況下優先使用靜態庫進行鏈接,若想指定鏈接的庫爲靜態庫或動態庫,可使用-l:加上庫文件全稱即可,如鏈接liblua.a,寫作-l:liblua.a。
g++ hello.cpp -o hello -L ./../lib1 -llua -L ./../lib2 -ljson -l:libtest.so
  1. ar -r參數,用於指定生成靜態鏈接庫。示例:
ar -r hello.a hello.o

值得一提的是,即便hello中存在main函數,對應靜態庫仍能正常生成。因爲在其他編譯過程中鏈接該靜態庫時,只會“按需”鏈接。
詳細情況可以查看這位博主的博客:自定義函數與靜態庫函數重名不衝突
10. -shared 參數,用於指定生成動態鏈接庫。示例:

g++ -o hello.so hello.cpp -shared -fPIC

部分系統在使用-shared參數時,是會自動補上-fPIC參數的,但也有些系統不會。
所以此處最好顯式標註-fPIC項,表明需要讓編譯器產生與位置無關代碼,即產生的代碼中全部使用相對地址,不是用絕對地址。
不過某些情況下不加-fPIC也能編譯生成動態庫,但條件比較苛刻。
(快給我住手這根本就不是動態鏈接)
其他參數讀者可自行查閱資料。

1.2 使用makefile編譯工程

在實際工程中,每次編譯工程時都重新輸入GCC編譯命令顯然過於憨憨。
所以我們需要通過腳本的形式,簡化編譯這一工作。其中,makefile就是一種用於編譯的特殊腳本。
makefile的語法規則比較簡單,主要爲:

目標:依賴項
	命令1(前面固定一個Tab縮進)
	命令2

比如在上個GCC使用示例中的hello.cpp編譯,我們可以在makefile中這樣編寫:

all:hello

hello:hello.cpp
	g++ -o hello hello.cpp

這樣,只需調用make all命令,就可以生成對應的hello可執行文件了。
直接調用make命令時,makefile默認只生成第一個目標文件即完成編譯,若像上述中調用make all,則makefile會編譯指定的目標all。
而目標all的依賴項爲hello,makefile就會檢查hello是否存在,或者hello是否存在對應的生成命令。
如此一來,當我們第二次調用make all命令時,makefile檢查到hello文件已存在,而之後也沒有需要執行的命令,就會直接返回。
實際工程中,爲了方便重複編譯,還會再增加clean目標:

clean:
	rm -fr *.o hello

這樣只需要調用make clean即可清除上一次產生的目標文件與中間文件,每次編譯的命令即可簡化爲make clean;make all。(大家可能注意到清除文件使用了linux的rm命令,事實上在makefile中是可以直接調用linux命令的)
但僅此而已,當工程項目中源文件較多時,再去一個個寫每個文件的名稱也是比較低效的。
對此我們可以使用makefile自帶的後綴規則.xxx.yyy,GCC會自動將xxx後綴的文件視爲源文件,yyy後綴的文件視爲目標文件,執行統一的生成命令(其中$*代表匹配的文件名中除了後綴的部分):

.cpp.o:
	g++ -o $*.o -c $*.cpp 

這樣,.cpp後綴的文件被匹配到時,對應.o後綴的目標文件生成規則就有了。不過單靠這一條還不能直接直接編譯,因爲編譯器需要知道你的源文件在哪。關於這個,makefile提供了wildcard函數可以用於獲取對應後綴的文件,再通過makefile的變量替換規則$(VAR:A=B),將文件後綴替換爲.o,就能得到中間文件的名稱了。如此一來,可編譯任意個數cpp文件的makefile就完成了:

SRC = $(wildcard *.cpp)
SRC_OBJ = $(SRC:.cpp=.o)

all:$(SRC_OBJ)
	g++ -o target.out $(SRC_OBJ)

clean:
	rm -fr *.o target.out

.cpp.o:
	g++ -o $*.o -c $*.cpp 

(一般將編譯的一二三步與第四步鏈接拆分爲兩批執行,多文件編譯可通過多線程提高編譯速度)

1.3 通過CMakeLists.txt生成makefile

雖然在上面的示例中,makefile的編寫已經存在部分的優化,但某些情況下仍然會存在編譯參數多、亂,無明確歸類的問題。
於是大佬們又想出了通過編寫簡單、規範的CMakeLists.txt來生成makefile文件的方式降低編寫難度。(套娃警告)
我們編寫CMakeLists.txt時的主要目標是設置相關編譯參數,剩餘的編譯步驟交由它自動生成。因此你大部分的操作實際上都是在設置各種參數。
如設置c/c++的編譯器SET(變量名 對應值):

SET(CMAKE_C_COMPILER /usr/local/bin/gcc540)
SET(CMAKE_CXX_COMPILER /usr/local/bin/g++540)

類似於CMAKE_C_COMPILER這種變量名都是CMake提前預設的名稱,如果你想設置一個自定變量,只需要名稱不與他們相同即可。
設置CMake的版本要求(必需項):

cmake_minimum_required(VERSION 2.6)

設置頭文件的包含目錄:

INCLUDE_DIRECTORIES(./../include)

設置目標文件的編譯參數:

ADD_DEFINITIONS(-g -Wall -fPIC -static-libstdc++ -static-libgcc)

設置庫文件的包含目錄與鏈接的目標庫文件:

LINK_DIRECTORIES(./../lib)
LINK_LIBRARIES(lua json)

獲取源文件,並設置當前的編譯目標爲生成庫文件:

AUX_SOURCE_DIRECTORY(./ SRC)
ADD_LIBRARY(target_name SHARED ${SRC})

AUX_SOURCE_DIRECTORY函數用於獲取某個目錄下所有的文件,用於ADD_LIBRARY中(表示源碼文件)。而根據其中的SHARED參數可確認產生的文件爲動態鏈接庫,最終生成的文件即爲target_name.so了。
其FIND_PATH/STRING/EXISTS等自帶函數大家可以自行查閱相關文件與用法。

End

關於Linux上c++的編譯就暫時寫到這,如果大家有在Win平臺上VS編譯工程的經驗,應該也能感覺到,Linux編譯與Win編譯過程中,需要設置的參數都是類似的。
???

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章