最近項目需求,需要在Linux下開發C++相關項目,經過一番摸索,簡單總結了一下如何通過VS Code進行編譯調試的一些注意事項。
關於VS Code在Linux下的安裝這裡就不提了,不管是CentOS還是Ubuntu,如果不懂且搜Q足夠的情況下,你會解決的。
在Windows下,如果你用Visual Studio進行開發,C/C++的編譯器一般采用微軟提供的MSBuild;在Linux下C/C++的編譯器大多采用gcc/g++。既然要在Linux下進行C++開發,很有必要了解一下g++編譯器的一些基本知識。
假設我現在有一個最簡單的C++文件:
1 #include <iostream> 2 using namespace std; 3 int main() { 4 cout << "Hello, world!!!!" << endl; 5 return 0; 6 }
接下來如何編譯呢?簡單來說分成兩步:先編譯,再鏈接
啟動終端,進入root模式,安裝gcc和g++
Ubuntu:
xxx@xxx:~$ sudo apt-get install build-essential
xxx@xxx:~$ gcc --version
xxx@xxx:~$ sudo apt-get install g++-4.8
xxx@xxx:~$ g++ --version
CentOS:
[xxx@xxx ~]$ su
[xxx@xxx ~]# yum install gcc
[xxx@xxx ~]# gcc --version
[xxx@xxx ~]# yum install g++
[xxx@xxx ~]# g++ --version
[xxx@xxx ~]$ g++ -c hello.cpp
輸出結果是一個hello.o文件,這是編譯過程的生成的中間文件。-c 表示只編譯,不鏈接。
[xxx@xxx ~]$ g++ -o hello.out hello.o
輸出結果是一個hello.out文件,這是最終的可執行文件。-o 表示輸出文件,hello.o是上一步生成的.o文件。
當然,如果第2、3步是可以合並執行,直接執行命令
[xxx@xxx ~]$ g++ -o hello.out hello.cpp
然而第2、3步分開執行是有意義的,後面會講到。
最後執行以下hello.out驗證一下輸出結果呗
[xxx@xxx ~]$ ./hello.out
實際開發過程中當然不可能只有一個cpp這麼簡單,有時候會有非常多的.h和.cpp文件相互配合,那麼上面直接通過g++編譯可執行文件就沒那麼簡單了。我們需要借助Make這個強大的項目構建工具,幫助我們構建和組織項目代碼。
假設現在有如下3個文件:hw2.cpp、solution.h和solution.cpp
1 /* solution.h */ 2 class Solution { 3 public: 4 void Say(); 5 };
1 /* solution.cpp */ 2 #include <iostream> 3 #include "solution.h" 4 void Solution::Say(){ 5 std::cout << "HI!" << std::endl; 6 }
1 /* hw2.cpp */ 2 #include "solution.h" 3 int main () { 4 Solution sln; 5 sln.Say(); 6 return 0; 7 }
可以看到這個簡單例子包括頭文件引用、定義和實現分離等情況,如果直接g++ -o hw2.out hw2.cpp將會報未定義引用的錯誤:
[xxx@xxx ~]$ g++ -o hw2.out hw2.cpp
/tmp/ccIMYTxf.o:在函數‘main’中:
hw2.cpp:(.text+0x10):對‘Solution::Say()’未定義的引用
collect2: 錯誤:ld 返回 1
這時Make就該大顯身手了。
首先我們還需要了解一下makefile。
在項目的根目錄下創建一個makefile文件,以告訴Make如何編譯和鏈接程序。
1 build : hw2.o solution.o 2 g++ -o build hw2.o solution.o #注意前面必須是tab,不能是空格 3 hw2.o : hw2.cpp solution.h 4 g++ -g -c hw2.cpp 5 solution.o : solution.h solution.cpp 6 g++ -g -c solution.cpp 7 clean : 8 rm hw2.o solution.o build
先來解釋一下makefile的基本語法規則:
target ... : prerequisites ... command #注意前面是tab
target是一個目標文件,可以是Object File,也可以是執行文件,還可以是一個標簽;
prerequisites是要生成那個target所需要的文件或是目標;
command是make需要執行的命令(任意的Shell命令)。
說白了就是target這一個或多個目標,依賴於prerequisites列表中的文件,其執行規則定義在command裡。如果prerequisites列表中文件比target要新,就會執行command,否則就跳過。這就是整個make過程的基本原理。
那麼,我們回頭看看上面定義的makefile文件,我們解釋一下每兩行的作用
1 build : hw2.o solution.o 2 g++ -o build hw2.o solution.o
target是build,依賴於hw2.o 和 solution.o,執行的命令是 g++ -o build hw2.o solution.o
意思是通過g++鏈接hw2.o和solution.o,生成可執行文件build,prerequisites有兩個.o文件,是因為代碼裡hw2引用了solution.h。
3 hw2.o : hw2.cpp solution.h 4 g++ -g -c hw2.cpp
target是hw2.o,依賴於hw2.cpp和solution.h,執行命令是g++ -g -c hw2.cpp
意思是通過g++編譯hw2.cpp文件,生成hw2.o文件,g++命令中 -g 表示生成的文件是可調試的,如果沒有-g,調試時無法命中斷點。
5 solution.o : solution.h solution.cpp 6 g++ -g -c solution.cpp
同上,編譯solution.cpp文件,生成solution.o文件。
7 clean : 8 rm hw2.o solution.o build
這裡clean不是一個可執行文件,也不是一個.o文件,它只不過是一個動作名字,類似於label的作用,make不會去找冒號後的依賴關系,也不會自動執行命令。如果要執行該命令,必須在make後顯示指出整個動作的名字,如make clean。
好了,接下來說一下make的工作原理。在默認的方式下,我們只需輸入make,則發生了以下行為:
a. make在當前目錄下找名為makefile或Makefile的文件;
b. 如果找到,它會找文件中的第一個target,如上述文件中的build,並作為終極目標文件;
c. 如果第一個target的文件不存在,或其依賴的.o 文件修改時間要比target這個文件新,則會執行緊接著的command來生成這個target文件;
d. 如果第一個target所依賴的.o文件不存在,則會在makefile文件中找target為.o的依賴,如果找到則執行command,.o的依賴必是.h或.cpp,於是make可以生成 .o 文件了
e. 回溯到b步執行最終目標
看一下執行結果
[xxx@xxx ~]$ make g++ -g -c hw2.cpp g++ -g -c solution.cpp g++ -o build hw2.o solution.o #注意前面必須是tab,不能是空格 [xxx@xxx ~]$ ./build
HI!
[xxx@xxx ~]$
由於makefile文件中加了-g這一選項,於是可以通過gdb進行調試,並且會命中斷點,這裡感興趣可以再了解一下gdb的使用。
接下來我們要說到如何通過VS Code進行調試。
首先安裝完VS Code之後,還需要安裝一下擴展cpptools,請自行完成。
點擊菜單 查看-> 調試,或直接快捷鍵ctrl + shift + D
點擊設置圖標,在彈出的選擇環境中選擇C++(GDB/LLDB),會自動創建一個launch.json文件
顧名思義,laucn.json的作用是告訴VS Code如何執行啟動任務,也就是我們要把什麼文件啟動起來,在上述例子中顯然是build這個可執行文件了。修改一下json文件中波浪線的program節點,改成${workspaceRoot}/build,其余的暫時不變
1 { 2 "version": "0.2.0", 3 "configurations": [ 4 { 5 "name": "C++ Launch", 6 "type": "cppdbg", 7 "request": "launch", 8 "program": "${workspaceRoot}/build", 9 "args": [], 10 "stopAtEntry": false, 11 "cwd": "${workspaceRoot}", 12 "environment": [], 13 "externalConsole": true, 14 "linux": { 15 "MIMode": "gdb" 16 }, 17 "osx": { 18 "MIMode": "lldb" 19 }, 20 "windows": { 21 "MIMode": "gdb" 22 } 23 }, 24 { 25 "name": "C++ Attach", 26 "type": "cppdbg", 27 "request": "attach", 28 "program": "${workspaceRoot}/build", 29 "processId": "${command.pickProcess}", 30 "linux": { 31 "MIMode": "gdb" 32 }, 33 "osx": { 34 "MIMode": "lldb" 35 }, 36 "windows": { 37 "MIMode": "gdb" 38 } 39 } 40 ] 41 }
接著我們嘗試一下F5,開始調試,結果可以看到報了一個缺少build文件的錯誤。原因是我們還沒執行make編譯出可執行文件呢。我們在launch.json文件中,添加一個preLaunchTask的節點,並設置值為“build”。注意這裡的build不是指可執行文件build,而是一個名為build的任務!
1 { 2 "version": "0.2.0", 3 "configurations": [ 4 { 5 "name": "C++ Launch", 6 "type": "cppdbg", 7 "request": "launch", 8 "program": "${workspaceRoot}/build", 9 "args": [], 10 "stopAtEntry": false, 11 "cwd": "${workspaceRoot}", 12 "environment": [], 13 "externalConsole": true, 14 "preLaunchTask": "build", 15 "linux": { 16 "MIMode": "gdb" 17 }, 18 "osx": { 19 "MIMode": "lldb" 20 }, 21 "windows": { 22 "MIMode": "gdb" 23 } 24 }, 25 { 26 "name": "C++ Attach", 27 "type": "cppdbg", 28 "request": "attach", 29 "program": "${workspaceRoot}/build", 30 "processId": "${command.pickProcess}", 31 "linux": { 32 "MIMode": "gdb" 33 }, 34 "osx": { 35 "MIMode": "lldb" 36 }, 37 "windows": { 38 "MIMode": "gdb" 39 } 40 } 41 ] 42 }
再嘗試F5,會提示一個信息:
點擊配置任務運行程序,並選擇Others, 會自動生成一個tasks.json文件,這個文件的作用就是告訴launch或者編譯器需要執行什麼操作。顯然我們這裡要執行make命令,修改tasks.json為如下:
1 { 2 "version": "0.1.0", 3 "command": "make", 4 "showOutput": "always", 5 "tasks": [ 6 { 7 "taskName": "clean" 8 }, 9 { 10 "taskName": "build", 11 "problemMatcher": { 12 "owner": "cpp", 13 "fileLocation": ["relative", "${workspaceRoot}"], 14 "pattern": { 15 "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", 16 "file": 1, 17 "line": 2, 18 "column": 3, 19 "severity": 4, 20 "message": 5 21 } 22 } 23 } 24 ] 25 }
其中tasks節點是一組任務,注意到其中一個名為build的任務,這就是launch.json文件中指定的preLaunchTask,表明在啟動可執行程序之前,會先執行一下preLaunchTask即這裡的build任務,重新make一下代碼,更新可執行程序之後再啟動。
當然也可以指運行tasks這些任務而不啟動可執行程序,直接ctrl + shift + B,在VSC的console裡可以看到和終端執行一樣的輸出:
執行完後,項目中會多出.o和build文件
關於VS Code的launch.json和tasks.json中更多節點的含義,參考
https://code.visualstudio.com/docs/editor/debugging
https://code.visualstudio.com/docs/editor/tasks
接著設置好斷點之後F5,就可以進入斷點調試了
--------------------------------------------------------------------------------------------------------------------------------------------------
本篇總結了gcc/g++和make/makefile的基礎知識,以及在Linux下使用VS Code進行調試開發的方法,希望對正在挖坑的同學有所幫助,坑避免一個是一個。