Author: Clivia Du
Completed Time:2021-9-20
First Review: 2021-11-28(美化、把圖片上傳了一下)
當我們想要實現一個大型的project,就會涉及多文件項目的編譯技術,在我們的機器人比賽中以及后續的碼農生涯中都是很必要的一環。下面就讓我們來看一看怎樣在VScode中實現多文件項目的編譯。
這里介紹三種實現方式:
1 逐條命令行
VScode與其他IDE不同的地方在於,VScode是一個可以用命令行控制程序的編輯器(事實上在配置VScode時安裝的各種插件就是一系列命令行功能的集合),對於熟悉ROS、Linux文件環境的各位來說,這種方式再親切不過。
我將以例子來具體說明。
首先,請在一個預置的文件夾下新建三個文件。
1 // hello.h 2 #ifndef HELLO_H 3 #define HELLO_H 4 5 #include <stdio.h> 6 7 extern void greet(const char *name); 8 9 #endif // HELLO_H 10 11 // hello.c 12 #include "hello.h" 13 14 void greet(const char *name) 15 { 16 printf("Hello, %s!\n", name); 17 } 18 19 // main.c 20 #include "hello.h" 21 22 int main(){ 23 greet("各位大佬!"); 24 }
這三個文件的邏輯是怎樣的呢?
首先我們在hello.h文件中聲明了greet函數,但是並沒有實現它。我們在hello.c文件中實現greet函數,在main.c函數中使用了該函數。
怎么編譯這個項目呢?
依次在界面下方的Terminal使用如下三個命令行。
1 gcc -c hello.c -o hello.o 2 gcc -c main.c -o main.o 3 gcc main.o hello.o -o main.exe
附注:像在matlab中一樣,clear可以幫助清除當前命令行中內容。
然后就可以運行程序main.exe了。

這三行命令都是什么意思呢?
這三行命令中,前兩行分別對hello.c和main.c文件做了預處理,編譯,匯編步驟,但是沒有做鏈接步驟(簡單起見,我們將前三個步驟統稱為編譯,於是我們稱稱這個過程為只進行編譯,而不鏈接)。最后一行,將mian.o, hello.o文件進行鏈接,最終得到可執行程序main.exe。
關於編譯、鏈接等有興趣可以自行查閱程序在計算機中的實現過程,本文不做討論。
對於例子中的hello程序來說,使用多文件未免大材小用,但是我們不可避免要用到很多開源的項目,它們項目大多具有非常多的文件,但編譯的過程基本上和前面的過程是一致的。
將項目分成多個文件,首先可以方便組織文件結構。比如將實現同一個功能的代碼放在一個文件內,不同功能的代碼放在不同的文件內。並且,如果修改其中一個文件,只需要重新編譯這一個文件,最后再重新鏈接就好了,可以減少編譯時間。
ps:我的配置中將exe置於程序文件夾下,不便之處在於需要單獨打開exe文件才能看到結果,而不是像之前一樣在底框看到。
2 Makefile
這個我們比較熟悉,是考核過程中接觸到的一種編譯手段。
如果一個項目有很多的文件,每個文件都要一個一個輸入命令編譯太麻煩了。GNU環境下的make提供了一個自動化做這些事情的工具。
首先我們需要在剛才的文件夾下新建一個名字為Makefile(注意大小寫)的文件,寫入如下內容
1 main.exe : main.o hello.o 2 gcc main.o hello.o -o main.exe 3 4 main.o : main.c hello.h 5 gcc -c main.c -o main.o 6 7 hello.o: hello.c hello.h 8 gcc -c hello.c -o hello.o

然后在終端輸入
make
就會自動完成上述編譯工作。
如果提示如下錯誤:
無法將“make”項識別為cmdlet、函數、腳本文件或可運行程序的名稱…
原因:
windows本身時沒有make命令的,在安裝MinGW后才會有和Linux中make命令具有相同作用的mingw32-make。 為了使用方便也可以把mingw32-make的名稱改為make。 但是如果在其它的編譯軟件中使用了mingw32-make的名稱的時候,也同樣需要將該軟件中的名稱改成make。
關於配mingw32的教程很多,就不多說。
以后我們修改文件的內容,只需要再用make命令就可以自動編譯了。而且make軟件可以自動檢測文件修改時間,對於上次編譯后沒有修改的文件不再進行編譯,從而減少編譯的消耗。
這一定程度上就是我們sudo make的原型,因為linux也是受GNU支持的。
關於如何寫Makefile,可以看看
3 Cmake
這也很有名了。
Makefile雖然已經集成化了命令,但是依舊很麻煩。現在有更現代的構建工具能夠幫助自動化生成Makefile文件,
apt-get install cmake
接下來就以一個實際的小工程為例,來講解怎么一步步編寫CMakeLists.txt文件,工程的目錄如下:
1 bplustree:. 2 │ CMakeLists.txt 3 │ coverage_build.sh 4 │ demo_build.sh 5 │ LICENSE 6 │ package.json 7 │ README.md 8 │ 9 ├─cmake 10 │ CodeCoverage.cmake 11 │ 12 ├─lib 13 │ bplustree.c 14 │ bplustree.h 15 │ CMakeLists.txt 16 │ 17 └─tests 18 bplustree_coverage.c 19 bplustree_demo.c 20 CMakeLists.txt 21 testcase_generator.py
bplustree是一個主目錄,在子目錄lib和tests里含有源文件,我們需要在這3個目錄下分別編寫CMakeLists.txt文件。
下面先來看主目錄的CMakeLists.txt文件,開頭用project來定義項目名稱,一般和主目錄名保持一致。
1 cmake_minimum_required (VERSION 2.6) 2 #定義項目名稱 3 project (bplustree) 4 #設置debug模式,如果沒有這一行將不能調試設斷電 5 set(CMAKE_BUILD_TYPE "Debug") 6 #設置debug模式下的編譯參數 7 set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb") 8 set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall") 9 #配置自定義模塊路徑,這里是bplustree /cmake 10 set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) 11 #配置庫名 12 set(LIB_BPLUSTREE_NAME bplustree) 13 #進入子目錄下執行 CMakeLists.txt文件 14 add_subdirectory(lib) 15 add_subdirectory(tests)
在lib目錄下,通過編譯bplustree.c文件生成動態庫和靜態庫,cmake代碼如下:
1 # LIB_BPLUSTREE_SRC是當前目錄下的源代碼文件集合 2 set(LIB_BPLUSTREE_SRC bplustree.c) 3 #這是最后庫文件的輸出目錄,這里是bplustree /build/lib 4 set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) 5 #添加編譯選項的宏定義_BPLUS_TREE_DEBUG 6 add_definitions(-D_BPLUS_TREE_DEBUG) 7 #生成一個名為bplustree的動態庫,即libbplustree.so 8 add_library(${LIB_BPLUSTREE_NAME} SHARED ${LIB_BPLUSTREE_SRC}) 9 #構建一個新的target,cmake會刪除其他同名的庫 10 #因此如果構建libbplustree.a,則會刪除libbplustree.so 11 # CLEAN_DIRECT_OUTPUT 1避免了出現這個問題 12 set_target_properties(${LIB_BPLUSTREE_NAME} PROPERTIES CLEAN_DIRECT_OUTPUT 1) 13 #構建動態庫版本號和api版本號libbplustree.so1.0和libbplustree.so1 14 set_target_properties(${LIB_BPLUSTREE_NAME} PROPERTIES VERSION 1.0 SOVERSION 1) 15 #把動態庫安裝到目錄bplustree /build/lib 16 install(TARGETS ${LIB_BPLUSTREE_NAME} LIBRARY DESTINATION ${LIBRARY_OUTPUT_PATH}) 17 #靜態庫和動態庫的名字不能相同,靜態庫需要在末尾加上_static 18 add_library(${LIB_BPLUSTREE_NAME}_static STATIC ${LIB_BPLUSTREE_SRC}) 19 #讓靜態庫輸出以libbplustree.a顯示而不是libbplustree_static.a 20 set_target_properties(${LIB_BPLUSTREE_NAME}_static PROPERTIES OUTPUT_NAME "${LIB_BPLUSTREE_NAME}") 21 set_target_properties(${LIB_BPLUSTREE_NAME}_static PROPERTIES CLEAN_DIRECT_OUTPUT 1) 22 #把靜態庫輸出到bplustree /build/lib 23 install(TARGETS ${LIB_BPLUSTREE_NAME}_static ARCHIVE DESTINATION ${LIBRARY_OUTPUT_PATH})
接下來構建目標
最后執行bplustree /test目錄下的CMakeLists.txt來構建最終的目標,構建的目標有2種,分別是bplustree_coverage和bplustree_demo,由CMAKE_BUILD_TYPE來決定具體使用哪一種。
1 #設置構建目標的名字 2 set(TEST_NAME ${PROJECT_NAME}_test) 3 set(COVR_NAME ${PROJECT_NAME}_coverage) 4 set(DEMO_NAME ${PROJECT_NAME}_demo) 5 #設置目標輸出目錄,在bplustree /build/bin 6 set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) 7 #設置頭文件所在目錄bplustree / lib 8 set(INC_DIR ${PROJECT_SOURCE_DIR}/lib) 9 include_directories(${INC_DIR}) 10 # 構建coverage目標 11 if(${CMAKE_BUILD_TYPE} MATCHES "Coverage") 12 set(LIB_DIR ../lib) 13 #設置構建目標所需的源文件 14 set(SRC_LIST ${LIB_DIR}/bplustree.c bplustree_coverage.c) 15 #構建可執行文件bplustree_coverage 16 add_executable(${COVR_NAME} ${SRC_LIST}) 17 #設置gcc的編譯選項 18 set(CMAKE_C_FLAGS "-O2 -Wall --coverage") 19 #使用自定義模塊CodeCoverage.cmake 20 include(CodeCoverage) 21 #執行自定義模塊里的函數,生成新的目標coverage 22 #第一個參數是新的目標名,最后編譯時要生成coverage目標, 23 #使用make coverage,無參數的make命令不會生成該目標 24 #第2個參數是上面生成的可執行文件 25 #第3個參數是新目標coverage的輸出文件 26 setup_target_for_coverage(coverage ${COVR_NAME} coverage) 27 else() 28 set(SRC_LIST bplustree_demo.c) 29 #生成bplustree_demo可執行文件 30 add_executable(${DEMO_NAME} ${SRC_LIST}) 31 set(CMAKE_C_FLAGS "-O2 -Wall -Werror -Wextra") 32 #將庫文件連接到目標,默認的是動態庫優先 33 #如果要鏈接靜態庫需要指明實際的文件 34 # target_link_libraries(${DEMO_NAME} libbplustree.a) 35 target_link_libraries(${DEMO_NAME} ${LIB_BPLUSTREE_NAME}) 36 endif() 37
PS:在makefile中的目標(target),相當於一個個段落標簽,從當標簽開始執行,到下一個標簽結束,如
1 all: 2 …… 3 coverager: 4 ……. 5 clean: 6 ……
上面的例子中,all、coverager和clean就是一個目標,默認make執行的是make all,需要執行其他目標時需要在make后聲明參數,如make coverager、make clean,此時將不會執行目標all里的語句。
果要更深程度的了解,可以
4 多語言混編(進階)
我們可能聽說過python是一種接口語言,可能也知道它對於其他語言有接口,到底是怎么回事呢?
鏈接技術給多語言混編提供了可能。而這種混編,也使得我們能夠對於編程中的不同情況進行不同的處理:如需要高性能能運算時交給C++去做,需要良好的人機交互體驗時則選擇python。
由於篇幅所限,我推薦大家閱讀
參考文章:
