基于VSCode和CMake进行C/C++开发

写在前面

本文为Linux下的C/C++开发笔记,参考b站视频BV1fy4y1b7TC

Linux指令

  • manhelp 查看指南(man = manual)
  • lstree 查看文件
ls -lah # 显示文件
# -l 表示列表显示
# -a 表示全部显示(包括隐藏文件)
# -h 表示可读显示(h = human-readable)
tree # 树形显示

开发环境搭建

  • 安装GCC、GDB(通常是自带的)
# 更新源
sudo apt update
# 通过以下命令安装
sudo apt install build-essential gdb
# 确认安装成功(-v = --version)
gcc -v
g++ -v
gdb -v
  • 安装CMake
# 通过以下命令安装
sudo apt install cmake
# 确认安装成功(-v = --version)
cmake -v

GCC编译器

VSCode通过调用GCC编译器实现C/C++编译:

  • gcc编译C
  • g++编译C++

编译过程

  • 预处理(Pre-Processing)
# -E 选项指示编译器仅对输入文件进行预处理,生成.i文件
g++ -E test.cpp -o test.i
  • 编译(Compiling)
# -S 选项指示编译器为C++代码产生汇编语言文件后停止编译,生成.s文件
g++ -S test.i -o test.s
  • 汇编(Assembling)
# -c 选项指示编译器把源代码编译成机器语言的目标代码,生成.o文件
g++ -c test.s -o test.o
  • 链接(Linking)
# -o 选项指示编译器生成可执行的二进制文件
g++ test.o -o test
编译过程

以上4个步骤可以一次性完成

# g++后面可以接多个.cpp文件
g++ test.cpp -o test

重要编译参数

  • -g 编译输出带调试信息的可执行文件
g++ -g test.cpp
  • -O[n] 优化源代码,n越大越快(n常为0-3)
# 使用-O2优化源代码(-O = -O1)
g++ -O2 test.cpp
# 量化运行时间
time ./test
  • -l/-L 指定库文件/库文件路径
# -l紧接库名,-L紧接库路径
# 在/lib和/usr/lib和/usr/local/lib里的库可直接用-l链接

# 链接glog库
g++ -lglog test.cpp

# 如果库文件不在上面三个目录里,需要用-L链接
# 链接libtest库,在/home/user/libfolder目录下
g++ -L/home/user/libfolder -llibtest test.cpp
  • -I 指定头文件搜索目录(大写i)
# 如果头文件不在/usr/include目录里,需要用-I指定
# 如-I/home/user/include,当前目录用-I.指定
g++ -I/home/user/include test.cpp
  • -Wall/-w 打印/关闭警告信息
# 打印警告
g++ -Wall test.cpp
# 关闭警告
g++ -w test.cpp
  • -std=c++11 设置编译标准
# 使用c++11标准编译
g++  -std=c++11 test .cpp
  • -o 指定输出文件名
# 指定输出文件名为test(不指定默认为a.out)
g++ test.cpp -o test
  • -D 定义宏

常用场景:
-DDEBUG 定义DEBUG宏,用-DDEBUG开启或关闭DEBUG

# include <stdio.h>

int main(){
    #ifdef DEBUG
        printf("DEBUG LOG\n");
    #endif
        printf("in\n");
}
# main.c中#ifdef DEBUG部分内容可被执行
gcc -DDEBUG main.c

编译和链接静态/共享库

假设目录结构如下:

include/Swap.h
main.cpp
src/Swap.cpp
  • 直接链接
g++ main.cpp src/Swap.cpp -Iinclude
  • 静态库
# 进入src
cd src
# 汇编,生成Swap.o (默认)
g++ Swap.cpp -c -I../include 
# 生成静态库libSwap.a(ar = archive)
ar rs libSwap.a Swap.o # lib开头就行
cd ..
# 链接,生成可执行文件static
g++ main.cpp -Iinclude -Lsrc -lSwap -o  static
  • 共享库
# 进入src
cd src
# 生成共享库libSwap.so(PIC = position independent code)
g++ Swap.cpp -I../include -fPIC -shared -o libSwap.so
## 上面的命令等价于以下两条命令
# g++ Swap.cpp -I../include -c -fPIC
# g++ -shared -o libSwap.so Swap.o
cd ..
# 链接,生成可执行文件shared
g++ main.cpp -Iinclude -Lsrc -lSwap -o shared

注意:运行共享库链接的可执行文件与前两种不同,需要指定库路径

# 运行共享库链接的可执行文件
LD_LIBRARY_PATH=src ./shared

GDB调试器

  • 编译时使用-g参数
  • 调试时执行gdb [可执行文件名]
  • 回车键重复上一命令
$(gdb)run(r) # 重新开始运行文件
# run argv[1] argv[2] 调试时命令行传参
$(gdb)list(l) [行号] # 查看某行为中心的源代码
# list [函数名]查看函数
# list 查看当前行为中心的源代码,再次list查看后续代码

$(gdb)break(b) [行号] # 在某行加入断点
$(gdb)delete(d) [断点号]# 删除断点
$(gdb)continue(c) # 继续循环
$(gdb)print(p) [变量名] # 打印值及地址
$(gdb)display [变量名] # 追踪查看变量
$(gdb)undisplay [追踪号] # 追踪查看变量
$(gdb)watch [变量名] # 设置观察变量,发生修改时打印

$(gdb)info(i) breakpoints(b) # 查看断点
$(gdb)info(i) display # 查看追踪点
$(gdb)info(i) watch # 查看观察点

$(gdb)next(n) # 单步调试,逐过程
$(gdb)step(s) # 单步调试,逐语句
$(gdb)finish # 跳出当前函数
$(gdb)quit(q) # 退出调试

CMake

  • 基本语法格式:指令(参数1 参数2)
  • 变量使用${}方式取值,但IF语句中直接使用变量名
set(HELLO hello.cpp) # 设置变量
add_executable(hello main.cpp ${HELLO}) # 使用变量
# IF语句中使用 IF(HELLO)
  • 指令大小写无关,参数和变量大小写相关

重要指令

假设目录结构如下:

include/libHelloSLAM.h
src/libHelloSLAM.cpp
useHello.cpp
  • cmake_minimum_required 声明要求的 cmake 最低版本
# 声明要求的 cmake 最低版本
cmake_minimum_required(VERSION 2.8)
  • project 声明一个 cmake 工程
# 声明一个 cmake 工程
# 语法:project(工程名 [支持的编程语言])
project(HelloSLAM)
  • set 定义变量
# 语法:set(变量名 值1 值2 ...)
# 设置编译模式(Debug相当于-g,Release相当于-O3)
set(CMAKE_BUILD_TYPE "Debug")
# 追加编译参数 
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
# 指定C++编译器
set(CMAKE_CXX_COMPILER "/usr/local/gcc/bin/g++")
# 可执行文件输出的存放路径(${PROJECT_SOURCE_DIR}为最近的CMakeLists.txt所在位置)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 库文件输出的存放路径(也可以用${CMAKE_SOURCE_DIR})
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
  • include_directories 添加头文件搜索路径(相当于g++的-I参数)
# 添加头文件搜索路径(相当于g++的-I参数)
include_directories(./include)
  • link_directories 添加库文件搜索路径(相当于g++的-L参数)
# 添加库文件搜索路径(相当于g++的-L参数)
link_directories(./lib) # 搜索额外的库文件
  • add_library 添加库
# 共享库 (不加SHARED是静态库)
add_library(hello_shared SHARED ./src/libHelloSLAM.cpp)
  • add_executable 添加可执行程序
# 添加可执行程序调用hello库中函数
# 语法:add_executable( 程序名 源代码文件 )
add_executable(useHello useHello.cpp)
  • target_link_libraries 将共享库文件链接到可执行程序上
# 将库文件链接到可执行程序上
target_link_libraries(useHello hello_shared)

编译指令

# 创建build文件夹
mkdir build
cd build
# 配置cmake(CMakeLists目录下)
cmake ..
# 编译
make

最后得到的目录结构

include/libHelloSLAM.h
src/libHelloSLAM.cpp
build/....(省略)
bin/useHello
lib/libhello_shared.so
useHello.cpp

VSCode调试

(1)如果前面已经编译好了,只需创建launch.json文件

  • Ctrl+Shift+D,点创建launch.json,选择C++(lldb/gdb)
  • Add configurations选择模板C/C++: (gdb) Launch
  • cwd改为“${workspaceFolder}”,即VSCode当前工作目录
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "let's debug",          // debug 文件名
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/bin/useHello",  // 构建后的可执行文件
            // ${workspaceFolder}是VSCode当前工作目录
            "cwd": "${workspaceFolder}",
            // 前面如果已经编译好了,前置任务可以注释掉
            "preLaunchTask": "let's build",  // 执行调试的前置任务名
            "MIMode": "gdb",               // Linux填gdb(与哪个 debugger 通信)
        }
    ]
}

(2)如果前面没有编译,可以执行前置任务,创建task.json文件

  • Terminal=>Configure Default Build Task,选择模板others
{
    "version": "2.0.0",
    "tasks": [
        {// 任务1——cmake
            "type": "shell",
            "label": "let's cmake",  // 任务名
            "command": "cmake",      // 命令行调用
            "args": [                // 命令行参数
                "../"
            ],
            "options": {
                "cwd": "${workspaceFolder}/build"  // 在 build/ 目录中执行
            }
        },
        {// 任务2——make
            "type": "shell",
            "label": "let's make",   // 任务名
            "command": "make",       // 命令行调用
            "options": {
                "cwd": "${workspaceFolder}/build"  // 在 build/ 目录中执行
            },
        },
        {// 任务1和2——build
            "label": "let's build",
            "dependsOrder": "sequence", // 按列出的顺序执行
            "dependsOn":[ // 依赖哪个任务的执行结果
                "let's cmake", 
                "let's make"
            ]
        }
    ]
}

(3)配置好launch.json文件和task.json文件之后,可以直接F5一键编译加调试

  • F5进入调试/继续调试
  • F10单步逐过程,F11单步逐语句
  • shift+F11跳出