智合網圖總目錄-各學院
關於智合網版主
智合網沿革
價值管理學院簡介
價值管理學院總目錄
價值管理簡介
價值管理總目錄
價值的定義

2020年7月31日 星期五

C++ 程式與專案的編譯建置(Compile & Build)程序方式

一個 C++ 程式原本對使用者而言是一個人類可以看得懂的高階語語,但是對於機器來說,機器是看不懂高階語言的,只能看得懂機器碼或低階語言,所以當一個 C++ 程式(此時稱為原始程式)撰寫完成後就須要將 C++ 的原始程式碼編譯 (compile) 或建置 (build) 成為機器碼 (machine code) 或稱為目的碼 (object code) 或有時也可稱為可執行碼 (executable code)。本文主要的範例執行環境為 Linux Ubuntu 18.04。

gcc/g++編譯器

一般而言,最常用的或最早使用的 C 程式編譯器為 gcc (General Compiler Collection),當 C++ 程式語言被發展出來之後,也可以延用繼續使用 gcc 編譯器,但是用 gcc 來編繹 C++ 程式時,使用者必須在 gcc 的指令後明確的指出要使用的 C++ 特別使用的各種函數庫與標頭檔,所以非常的麻順,所以特別發展了另一個編譯器 g++,此一 g++ 編譯器預設會先去讀取 C++ 特別使用的各種函數庫與標頭檔,所以就很方便的的編譯 C++ 程式了。



然而實際上,據 gcc 的線上文件中的 link options 以及文 how g++ is invoked 文章的說明:

g++ 此一指令等同於指令 gcc -xc++ -lstdc++ -shared-libgcc 

其中,第1個參數是編譯的參數(compiler option),第2及第3個參數是連結參數(linker option)。

gcc 與 g++ 兩者之間的差異也可以列出如下:
  1. gcc : GNU C Complier
  2. g++: GNU C++ Complier
  3. gcc 編譯 *.c 成為 C 程式機器碼,但編譯 *.cpp 為 C++ 程式機器碼亦即通稱的目的檔案 (object file)。
  4. g++ 編譯 *.c 及 *.cpp 皆為 C++ 程式機器碼。
  5. g++ 編譯明若連結已編譯的目的檔案(object files )時,皆會連結標準的 C++ 程式庫 (std C++ libries),但是 gcc 並不會如此處理。
  6. gcc 編譯 C 程式時使用比較少的事先定義的巨集碼 (predefined macros).
  7. gcc 編譯 *.cpp 程式,以及 g++ 編譯 *.c 及 *.cpp 程式時,會使用較多額外的巨集碼 (extra macros)。

一般而言,gcc 在編譯一個 C 程式時,最簡單的指令為:


gcc source.c -o object.o
 

也就是說,利用參數 -o 來指令編譯後的目的檔案名稱為 object.o,而且其實目的檔案不必特別給定附檔名,同時目的檔本身就是一個可執行檔 (executable file)。

但是利用 gcc 編譯一個 C++ 程式時,若使用上述簡單的指令:


gcc source.cpp -o object.o


往往就會出現編譯錯誤的訊息,因為通常一個 C++ 程式中所使用的預設相關函數不是一個 C 程式中所使用的,所以 gcc 不會主動去尋找 C++ 預設使用的函數庫,所以自然就會產生編譯錯誤的訊息。若真得想用 gcc 指令來編譯一個 C++ 程式,就要如上述對 gcc 與 g++ 之異同說明段落中所言,要特別加上特定的參數才可以,所以顯然比較麻煩。

但是若使用 g++ 編譯一個 C++ 程式時,若使用下述簡單的指令:


g++ source.cpp -o object.o
 

那麼就可以順利的繹譯成功了。如此可見,當要編譯一個 C++ 程式時,直接使用 g++ 指令是較適當的。

當然,要使用 gcc 及 g++時,就必須要先安裝這些套件,一般而言要安裝
gcc 及 g++的指令為:

sudo apt-get install gcc, build-essential
 

如此 gcc 及 g++ 就會被同時安裝。

cmake 及 make 指令

如前所述,gcc, g++ 的編譯程式是可以直接編譯 C/C++ 程式成目的檔案機器碼/可執行檔,但是若所撰寫的是一個比較複雜的專案程式,例如,同時包括多個甚至10幾個程式,或是含入別的專案時,那麼利用 gcc, g++ 的指令時,就要在此指令中寫了一大堆的檔案名稱與相關的參數時,對於程式設計師而言是很麻煩的,當然有人就想說,那可以另外寫一個 c shell 的程式來管理這一個編譯的過程,這種做法當然可以,但是使用過程中仍舊有些不方便,所以就有出現了一個 cmake 及 make 程式的概念。

cmake (Cross-Platform Makefile Generator跨平台建置檔案生成器) 主要的工作,就是「自動的」對要編譯的一群原始程式進行編譯前的解析與函數之間連結的串連分析,所以是一個 preprocessing 的工作,也就是說事先決定好這一個專案中依序要編譯那些程式,以及這些程式中要使用到那些函數庫等,然後產生一群解析後的關連檔案群,這些檔案通稱為 makefiles,而且或可稱為編譯解析檔。當解析沒有錯誤,就可以進行下一個動作,就是真正進行編譯成目的檔案的工作。

所以,
  1. cmake 指令是進行編譯前的前序解析作業,產生 makefiles 等解析檔。
  2. make 指令就是將cmake 所產生的 makefiels 解析檔進行處理,將之轉成目的檔案的工作。

cmake 的優點之一,就是若程式群中只有少數幾個程式有修正過,那麼 cmake 就只會標註,只重新解析與編譯這些修正的程式,所以可以增進整個編譯的效率。 

顯然 cmake 與 make 指令遠比直接使用 gcc, g++ 指令更有彈性更有效率。

若要使用 cmake 與 make 指令當然要安裝這些相關套件,當初安裝 gcc, build-essential 套件時並不包括 cmake 套件,所以必須要窗外安裝,安裝 cmake 套件的指令如下:

sudo apt install cmake


但是在執行 cmake 之前,要先建立(通常是人工建立或維護)一個 CMakeLists.txt 的檔案,此一檔案要設定如下的資料,包括,專案的名稱,目的檔名,各原始檔名等,而且要與原始程式檔在同一個目錄,此一 CMakeLists.txt 的範例內容如下:


cmake_minimum_required (VERSION 2.6)
project (LeapYear)
add_executable(LeapYear LeapYear.cpp) 


其中 project (LeapYear) 中的 LeapYear 是專案的名稱,而
add_executable(LeapYear LeapYear.cpp) 中的第一個參數 LeapYear 是可執行檔名稱,第二個參數 LeapYear.cpp 是原始程式名稱。

當執行 cmake 指令時,可以進入包括原始程式與
CMakeLists.txt 檔案的目錄後,下達如下的指令:

cmake .

其中,. 是代表現前的目錄之意,意即從目錄的目錄開始尋找相關的檔案來進行編譯前的處理。

若是原始程式檔CMakeLists.txt 檔案假設存放在 src 目錄中,那麼就在終端機中,移到 src 目錄的上一層目錄,然後
下達如下的指令:

cmake src

cmake -G Ninja


cmake 與 make 指令其實是一種建構系統 (build system),用以最終建立一個可執行檔,當然也包括其他的建構系統,例如 eclipse CDT managed build system。 在 eclipse CDT 中,當以 「File/New/C/C++ Project/CMake Project」建立一個要以cmake 來編譯的C++程式專案後,其預設的 camke 指令如下:

cmake -G Ninja

如前所述,cmake (Cross-Platform Makefile Generator;跨平台建置檔案生成器)主要的目的是建立一個適合各種平台(例如,Linux, Windows, MacOS) 下的 makefiles 建置檔案的解析檔,其中就包括了各標頭檔 (header files) 之間的相依分析 (dependency analysis)。當這些 makefiles 被建立後就可以再透過一個機器碼或目的碼的軟體套件,稱為機器碼生成器 (generator) 來最後產生適合不同平台下可執行的機器碼檔。 這些可執行檔的生成器有多個,最常見的就是 make generator,另外一個就是在2007年被一個開發 ChromeOS 的工程師所發展出來的 ninja generator,ninja 標榜的是小而高效。

那麼在執行 cmake 指令時,要如何指定要產生的 makefiles 是符合那一個 generator 所看得懂的格式呢?從以下的 cmake 指令語法即可以看出:

cmake <srccode_dir> --build <build_dir> -G <generator>
 

其中
  • srccode_dir 指的就是原始碼的路徑,這是必須要指定的。
  • --build 參數後要指定的是建置成目的檔後要存放目的檔的路徑,預設是 build 目錄。
  • -G 參數就是用來指定要依據那一個目的碼生成器的規格來產生 makefiles 解析檔。例如可以指定成 -G Ninja ,那麼就會產生 ninja generator 所看的懂的 makefiles ,若不指定那一個特定的生成器,那麼在 Linux 平台下,就是產生標準的 Unix Makefiels;在Windows 平台下其預設的生成器平台就是 Visual Studio 的某一版本,例如 Visual Studio 12 2013 Win64

但是當我們建立一個 「File->New->C++ Project and "Empty or Existing CMake Project」 後,輸入一個簡單的HelloWorld專案,並執行「Project/Build Project」時,卻出現如下的錯誤訊息:

Building in: /home/aaa/eclipse-workspace/LeapYear/build/default
Configuring in: /home/aaa/eclipse-workspace/LeapYear/build/default
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON /home/aaa/eclipse-workspace/LeapYear


CMake Error: CMake was unable to find a build program corresponding to "Ninja".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.-- Configuring incomplete, errors occurred!

CMake Error: CMAKE_C_COMPILER not set, after EnableLanguageSee also "/home/lpy/eclipse-workspace/LeapYear/build/default/CMakeFiles/CMakeOutput.log".

CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage


上述的訊息中,最重要的內容是:CMake Error: CMake was unable to find a build program corresponding to "Ninja".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.-- Configuring incomplete, errors occurred!,也就是說 eclipse 在利用 cmake 指令建立各 makefiles 時,目前指令是使用 ninja generator (build program),因為前一行訊息指令為:cmake -G Ninja …,但是系統卻找不到這一個 ninja generator 套件。

當發生此一錯誤訊息時,要進行如下的處理:
  1. 確認是否已安裝 cmake 套件。查詢的指令為:cmake -h。安裝套件的指令為:sudo apt install cmake
  2. 確認是否已安裝 ninja 套件。
    查詢的指令為:ninja -h。安裝套件的指令為:sudo apt install ninja-build
一般而言,若已安裝 cmake 及 ninja 套件後,就不會再出現錯誤訊息了。

而且在執行「Project/Build Project」時,在console頁框中我們可以從以下的訊息分析:

Building in: /home/aaa/eclipse-workspace/LeapYear/build/default
cmake --build . -- -v
[1/2] /usr/bin/c++    -O3 -DNDEBUG -MD -MT CMakeFiles/LeapYear.dir/LeapYear.cpp.o -MF CMakeFiles/LeapYear.dir/LeapYear.cpp.o.d -o CMakeFiles/LeapYear.dir/LeapYear.cpp.o -c ../../LeapYear.cpp
[2/2] : && /usr/bin/c++  -O3 -DNDEBUG  -rdynamic CMakeFiles/LeapYear.dir/LeapYear.cpp.o  -o LeapYear   && :
Build complete (0 errors, 0 warnings): /home/aaa/eclipse-workspace/LeapYear/build/default
 

雖然因為所使用的cmake指令中並沒有出現 cmake ... -G Ninja ... 特別指定使用 ninja generator 格式的指令,而只顯示 cmake --build . -- -v ,但其實在專案的目錄下會額外產生一個 build 的子目錄,而在其目錄下又會產生一個 default 子目錄,在此一目錄下包括如下的檔案:

CMakeFiles
build.ninja
compile_commands.json
rules.ninja
CMakeCache.txt
cmake_install.cmake

LeapYear

其中 CMakeFiles 是一個子目錄,而很明顯的可以看出一些檔名(附檔名)出現了 ninja 的名稱,這代表確實 cmake 指令產生了可以被 ninja object code generator 所看得懂的 makefiles。

而且其中的 LeapYear 其實就是最終所產生的目的檔/可執行檔。可在終端機中進入此一build/default目錄後下達指令:

./LeapYear

即可以執行此一執行檔。


結語

對於 eclipse CDT 而言,其系統是存在一個 autotool (一個概念的通稱,非實際有此一名稱的套件) 的build套件用以自動(或完全掌控)解析原始程式成為一個解析檔 (makefiels),並最終建立可執行檔。

在 cmake 系統中,其實其與 CDT的關係如下:
  1. cmake 指令用來取代 eclipse CDT中的 autotool。產生 Unix makefiles 。
  2. make 指令產生最終的目的檔/可執行檔。
  3. 通常會在專案的目錄下產生一個 build 子目錄,以儲存編譯過程中的解析檔與目的檔。
  4. 原始程式會直接放在專案的目錄下,同時要與 CMakeLists.txt 檔案在同一目錄下。
在cmake -G Ninja 系統中,其實其與 CDT的關係如下:
  1. cmake -G Ninja 指令用來取代eclipse CDT中的 autotool。產生 ninja  makefiles。
  2. ninja 指令產生最終的目的檔/可執行檔。
  3. 通常會在專案的目錄下產生一個 build 子目錄,以儲存編譯過程中的解析檔與目的檔。
  4. 原始程式會直接放在專案的目錄下,同時要與 CMakeLists.txt檔案在同一目錄下。  

而若使用 eclipse CDT 自己管理維護的 managed build system下的特性:
  1. 在專案的目錄下,原始程式碼一律要放在 src 子目錄下,若是一開始是建立一的空的專案,那麼之後要新建每一個原始程式時,就要人工的先建立 src 子目錄,然後再將每一個原始程式存放在 scr 子目錄下。
  2. 在 CDT managed build system 中,不存在(不需要) cmake 系統中專用的 CMakeLists.txt。
  3. CDT managed build system 在編譯時,會在專案的目錄下建立一個 Debug 子目錄,此一目錄就是存放解析檔與目錄檔,例如,名為 HelloWorld 專案目錄下的 Debug 子目錄的內容如下:
001-HelloWorld
makefile
objects.mk
sources.mk
src

其中 src 為一個子目錄的名稱,內容存放 *.d, *.o, *.mk的檔案,而makefile 就是解析的設定檔,001-HelloWorld就是目的檔/可執行檔。

簡言之,使用 eclipse CDT build system 管理的專案似乎是比較方便的。 

也就說,當點按「File/New/C/C++ Project」 後可以有如下的選項:
  1. Arduino C++ Sketch: A single C++ file with empty setup() and loop() functions. 也就是產生一個 Arduino C++ 架構程式的專案檔,其中只包括一個 setup() 與 loop() 兩個空的函數架構空間,其內容之後必須由使用者自行撰寫。
  2. C Manged Build: A C Project build using the CDT's managed build system. 也就是建立一個由 CDT編譯建直系統自行管理的 C 程式專案。
  3. C++ Managed Build: A C++ Project build using the CDT's managed build system. 也就是建立一個由 CDT編譯建直系統自行管理的 C++ 程式專案。在進入後,點按「Executable/Empty Project」或是「Executable/ Hello World C++ Project」。此時建議選擇
    Executable/ Hello World C++ Project」。又對於 Toolchains 區塊的選擇,可以是 Cross GCC 或是 Linux GCC,建議選擇 Cross GCC。
  4. CMake Project: A CMake project with a Hello World executable to get started. 也就是產生一個 Hello World 範本的 CMake 專案。預設的機器碼生成器為 ninja。
  5. Empty or Existing CMake Project: Create a CMake project with no files. Can be used to create one over existing content. 也就是產生一個 空的 CMake 專案。預設的機器碼生成器為 ninja。
也就是建議往後開發一個 C++ 專案時,要選擇C++ Managed Build: A C++ Project build using the CDT's managed build system. 亦即建立一個由 CDT自行管理的編譯建置系統的 C++ 程式專案。在進入後,點按「Executable/Empty Project」或是「Executable/ Hello World C++ Project」。此時應該選擇
「Executable/ Hello World C++ Project」。這是因為若只選擇
「Executable/Empty Project」,那麼系統並不會自動建立一個存放原始程式的 src 目錄,也就是之後,使用者必須要自行人工的建立此一 src 目錄,甚至將之後建議的 C++ 原始程式檔案以人工的方式移動到此一 src 目錄。

若是選擇「Executable/ Hello World C++ Project」,那麼就會自動產生 src 目錄,並將一個範本檔案的 C++ 程式檔建立起來,當然在建立此一範本檔案時,專案的名稱與原始程式的名稱是可以由使用者自行給定新的名稱,而非一定是會產生一個名為 HelloWorld 的專案與原始程式名稱。

又對於 Toolchains 區塊的選擇,可以是 Cross GCC 或是 Linux GCC,建議選擇 Cross GCC,也就是可以選擇跨平台的機器碼生成器 (generator)。

又上述所使用到的 C++ 編譯相關套件的安裝指令如下:

  1. gcc, g++ 編譯套件安裝指令:sudo apt install gcc, build-essential
  2. cmake 套件安裝指令:sudo apt install cmake
  3. ninja 套件安裝指令:sudo apt install ninja-build



主要網頁類別

  1. 關於智合網