Linux中使用VS Code編譯調(diào)試C++項(xiàng)目詳解
前言
關(guān)于VS Code在Linux下的安裝這里就不提了,不管是CentOS還是Ubuntu,如果不懂且搜問(wèn)題足夠的情況下,你會(huì)解決的。
一、前置知識(shí)——gcc/g++的編譯鏈接過(guò)程
在Windows下,如果你用Visual Studio進(jìn)行開(kāi)發(fā),C/C++的編譯器一般采用微軟提供的MSBuild;在Linux下C/C++的編譯器大多采用gcc/g++。既然要在Linux下進(jìn)行C++開(kāi)發(fā),很有必要了解一下g++編譯器的一些基本知識(shí)。
假設(shè)我現(xiàn)在有一個(gè)最簡(jiǎn)單的C++文件:
#include <iostream> using namespace std; int main() { cout << "Hello, world!!!!" << endl; return 0; }
接下來(lái)如何編譯呢?簡(jiǎn)單來(lái)說(shuō)分成兩步:先編譯,再鏈接
1. 安裝g++編譯器
啟動(dòng)終端,進(jìn)入root模式,安裝gcc和g++
Ubuntu:
CentOS:
[xxx@xxx ~]$ su [xxx@xxx ~]# yum install gcc [xxx@xxx ~]# gcc --version [xxx@xxx ~]# yum install gcc-g++ [xxx@xxx ~]# g++ --version
2. 編譯hello.cpp
[xxx@xxx ~]$ g++ -c hello.cpp
輸出結(jié)果是一個(gè)hello.o文件,這是編譯過(guò)程的生成的中間文件。-c 表示只編譯,不鏈接。
3. 鏈接hello.o生成hello.out
[xxx@xxx ~]$ g++ -o hello.out hello.o
輸出結(jié)果是一個(gè)hello.out文件,這是最終的可執(zhí)行文件。-o 表示輸出文件,hello.o是上一步生成的.o文件。
當(dāng)然,如果第2、3步是可以合并執(zhí)行,直接執(zhí)行命令
[xxx@xxx ~]$ g++ -o hello.out hello.cpp
然而第2、3步分開(kāi)執(zhí)行是有意義的,后面會(huì)講到。
4. 運(yùn)行hello.out
最后執(zhí)行以下hello.out驗(yàn)證一下輸出結(jié)果唄
[xxx@xxx ~]$ ./hello.out
二、構(gòu)建項(xiàng)目
實(shí)際開(kāi)發(fā)過(guò)程中當(dāng)然不可能只有一個(gè)cpp這么簡(jiǎn)單,有時(shí)候會(huì)有非常多的.h和.cpp文件相互配合,那么上面直接通過(guò)g++編譯可執(zhí)行文件就沒(méi)那么簡(jiǎn)單了。我們需要借助Make這個(gè)強(qiáng)大的項(xiàng)目構(gòu)建工具,幫助我們構(gòu)建和組織項(xiàng)目代碼。
假設(shè)現(xiàn)在有如下3個(gè)文件:hw2.cpp、solution.h和solution.cpp
/* solution.h */ class Solution { public: void Say(); };
/* solution.cpp */ #include <iostream> #include "solution.h" void Solution::Say(){ std::cout << "HI!" << std::endl; }
/* hw2.cpp */ #include "solution.h" int main () { Solution sln; sln.Say(); return 0; }
可以看到這個(gè)簡(jiǎn)單例子包括頭文件引用、定義和實(shí)現(xiàn)分離等情況,如果直接g++ -o hw2.out hw2.cpp將會(huì)報(bào)未定義引用的錯(cuò)誤:
[xxx@xxx ~]$ g++ -o hw2.out hw2.cpp
/tmp/ccIMYTxf.o:在函數(shù)‘main'中:
hw2.cpp:(.text+0x10):對(duì)‘Solution::Say()'未定義的引用
collect2: 錯(cuò)誤:ld 返回 1
這時(shí)Make就該大顯身手了。
首先我們還需要了解一下makefile。
在項(xiàng)目的根目錄下創(chuàng)建一個(gè)makefile文件,以告訴Make如何編譯和鏈接程序。
build : hw2.o solution.o g++ -o build hw2.o solution.o #注意前面必須是tab,不能是空格 hw2.o : hw2.cpp solution.h g++ -g -c hw2.cpp solution.o : solution.h solution.cpp g++ -g -c solution.cpp clean : rm hw2.o solution.o build
先來(lái)解釋一下makefile的基本語(yǔ)法規(guī)則:
target ... : prerequisites ... command #注意前面是tab
target是一個(gè)目標(biāo)文件,可以是Object File,也可以是執(zhí)行文件,還可以是一個(gè)標(biāo)簽;
prerequisites是要生成那個(gè)target所需要的文件或是目標(biāo);
command是make需要執(zhí)行的命令(任意的Shell命令)。
說(shuō)白了就是target這一個(gè)或多個(gè)目標(biāo),依賴于prerequisites列表中的文件,其執(zhí)行規(guī)則定義在command里。如果prerequisites列表中文件比target要新,就會(huì)執(zhí)行command,否則就跳過(guò)。這就是整個(gè)make過(guò)程的基本原理。
那么,我們回頭看看上面定義的makefile文件,我們解釋一下每?jī)尚械淖饔?/p>
build : hw2.o solution.o g++ -o build hw2.o solution.o
target是build,依賴于hw2.o 和 solution.o,執(zhí)行的命令是 g++ -o build hw2.o solution.o
意思是通過(guò)g++鏈接hw2.o和solution.o,生成可執(zhí)行文件build,prerequisites有兩個(gè).o文件,是因?yàn)榇a里hw2引用了solution.h。
hw2.o : hw2.cpp solution.h g++ -g -c hw2.cpp
target是hw2.o,依賴于hw2.cpp和solution.h,執(zhí)行命令是g++ -g -c hw2.cpp
意思是通過(guò)g++編譯hw2.cpp文件,生成hw2.o文件,g++命令中 -g 表示生成的文件是可調(diào)試的,如果沒(méi)有-g,調(diào)試時(shí)無(wú)法命中斷點(diǎn)。
solution.o : solution.h solution.cpp g++ -g -c solution.cpp
同上,編譯solution.cpp文件,生成solution.o文件。
clean : rm hw2.o solution.o build
這里clean不是一個(gè)可執(zhí)行文件,也不是一個(gè).o文件,它只不過(guò)是一個(gè)動(dòng)作名字,類似于label的作用,make不會(huì)去找冒號(hào)后的依賴關(guān)系,也不會(huì)自動(dòng)執(zhí)行命令。如果要執(zhí)行該命令,必須在make后顯示指出整個(gè)動(dòng)作的名字,如make clean。
好了,接下來(lái)說(shuō)一下make的工作原理。在默認(rèn)的方式下,我們只需輸入make,則發(fā)生了以下行為:
a. make在當(dāng)前目錄下找名為makefile或Makefile的文件;
b. 如果找到,它會(huì)找文件中的第一個(gè)target,如上述文件中的build,并作為終極目標(biāo)文件;
c. 如果第一個(gè)target的文件不存在,或其依賴的.o 文件修改時(shí)間要比target這個(gè)文件新,則會(huì)執(zhí)行緊接著的command來(lái)生成這個(gè)target文件;
d. 如果第一個(gè)target所依賴的.o文件不存在,則會(huì)在makefile文件中找target為.o的依賴,如果找到則執(zhí)行command,.o的依賴必是.h或.cpp,于是make可以生成 .o 文件了
e. 回溯到b步執(zhí)行最終目標(biāo)
看一下執(zhí)行結(jié)果
[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這一選項(xiàng),于是可以通過(guò)gdb進(jìn)行調(diào)試,并且會(huì)命中斷點(diǎn),這里感興趣可以再了解一下gdb的使用。
接下來(lái)我們要說(shuō)到如何通過(guò)VS Code進(jìn)行調(diào)試。
三、在VS Code中編譯調(diào)試
首先安裝完VS Code之后,還需要安裝一下擴(kuò)展cpptools,請(qǐng)自行完成。
點(diǎn)擊菜單 查看-> 調(diào)試,或直接快捷鍵ctrl + shift + D
點(diǎn)擊設(shè)置圖標(biāo),在彈出的選擇環(huán)境中選擇C++(GDB/LLDB),會(huì)自動(dòng)創(chuàng)建一個(gè)launch.json文件
顧名思義,laucn.json的作用是告訴VS Code如何執(zhí)行啟動(dòng)任務(wù),也就是我們要把什么文件啟動(dòng)起來(lái),在上述例子中顯然是build這個(gè)可執(zhí)行文件了。修改一下json文件中波浪線的program節(jié)點(diǎn),改成${workspaceRoot}/build,其余的暫時(shí)不變
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,開(kāi)始調(diào)試,結(jié)果可以看到報(bào)了一個(gè)缺少build文件的錯(cuò)誤。原因是我們還沒(méi)執(zhí)行make編譯出可執(zhí)行文件呢。我們?cè)趌aunch.json文件中,添加一個(gè)preLaunchTask的節(jié)點(diǎn),并設(shè)置值為“build”。注意這里的build不是指可執(zhí)行文件build,而是一個(gè)名為build的任務(wù)!
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,會(huì)提示一個(gè)信息:
點(diǎn)擊配置任務(wù)運(yùn)行程序,并選擇Others, 會(huì)自動(dòng)生成一個(gè)tasks.json文件,這個(gè)文件的作用就是告訴launch或者編譯器需要執(zhí)行什么操作。顯然我們這里要執(zhí)行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節(jié)點(diǎn)是一組任務(wù),注意到其中一個(gè)名為build的任務(wù),這就是launch.json文件中指定的preLaunchTask,表明在啟動(dòng)可執(zhí)行程序之前,會(huì)先執(zhí)行一下preLaunchTask即這里的build任務(wù),重新make一下代碼,更新可執(zhí)行程序之后再啟動(dòng)。
當(dāng)然也可以指運(yùn)行tasks這些任務(wù)而不啟動(dòng)可執(zhí)行程序,直接ctrl + shift + B,在VSC的console里可以看到和終端執(zhí)行一樣的輸出:
執(zhí)行完后,項(xiàng)目中會(huì)多出.o和build文件
關(guān)于VS Code的launch.json和tasks.json中更多節(jié)點(diǎn)的含義,參考
https://code.visualstudio.com/docs/editor/debugging
https://code.visualstudio.com/docs/editor/tasks
接著設(shè)置好斷點(diǎn)之后F5,就可以進(jìn)入斷點(diǎn)調(diào)試了
總結(jié)
本文主要總結(jié)了gcc/g++和make/makefile的基礎(chǔ)知識(shí),以及在Linux下使用VS Code進(jìn)行調(diào)試開(kāi)發(fā)的方法,希望對(duì)正在挖坑的同學(xué)有所幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
C語(yǔ)言函數(shù)的遞歸和調(diào)用實(shí)例分析
一個(gè)函數(shù)在它的函數(shù)體內(nèi)調(diào)用它自身稱為遞歸調(diào)用。這種函數(shù)稱為遞歸函數(shù)。C語(yǔ)言允許函數(shù)的遞歸調(diào)用。在遞歸調(diào)用中,主調(diào)函數(shù)又是被調(diào)函數(shù)。執(zhí)行遞歸函數(shù)將反復(fù)調(diào)用其自身,每調(diào)用一次就進(jìn)入新的一層2013-07-07C++ float、double判斷是否等于0問(wèn)題
這篇文章主要介紹了C++ float、double判斷是否等于0問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08C++ 詳解數(shù)據(jù)結(jié)構(gòu)中的搜索二叉樹(shù)
搜索二叉樹(shù)是一種具有良好排序和查找性能的二叉樹(shù)數(shù)據(jù)結(jié)構(gòu),包括多種操作,本篇只介紹插入,排序(遍歷),和刪除操作,重點(diǎn)是刪除操作比較復(fù)雜2022-04-04C語(yǔ)言編程之動(dòng)態(tài)內(nèi)存與柔性數(shù)組的了解
本文是C語(yǔ)言編程篇,這篇文章主要為大家介紹了C語(yǔ)言編程中動(dòng)態(tài)內(nèi)存的函數(shù)與柔性數(shù)組的特點(diǎn),有需要的朋友可以借鑒參考下,希望可以有所幫助2021-09-09C++實(shí)現(xiàn)LeetCode(118.楊輝三角)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(118.楊輝三角),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07實(shí)現(xiàn)Dijkstra算法最短路徑問(wèn)題詳解
這篇文章主要介紹了實(shí)現(xiàn)Dijkstra算法最短路徑問(wèn)題詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08