发布于  更新于 

C/C++ in VSCode 开发环境配置指南 1.3

本文主要介绍在 VSCode 中配置 C、C++ 开发、调试环境的方式。

前言

网上已经有很多《配置 VSCode 中的 C/C++ 开发和环境》这样的博客了,但博主依旧大清早爬起来写这篇博客的主要原因有3个:

  • 博主经常给自己和别人的新系统配置该环境;
  • 博主每次也很烦这个配置过程,希望能有可以复制粘贴后稍作修改即可用模板;
  • 网上这样的博客大多只是把配置文件贴出来,并没有解释每条配置的意义,导致其很难被客制化。

恰逢博主的很多朋友配置这一环境一直出问题,特此开坑,简单介绍在 VSCode 中配置 C/C++ 开发环境的方法!

更新日志

2021-02-15 1.0 开坑篇

2021-02-17 1.1 补充性更新

  • 更新了 2 个常见问题
    • 无法找到输出到控制台的内容
    • undefined reference to Init()

2021-04-02 1.2 修正性更新

  • 根据 Micrsoft Docs 修正了部分拓展语法的说明

2021-08-15 1.3 补充性更新(半年了!)

  • 增加了适用于多文件编译(默认 main 函数为入口点)的配置
  • 增加了调整可执行文件存放位置的特别说明

本文主要针对 Windows 系统进行说明,如果您正在使用 Linux 系统,可以无视以下配置文件中的 .exe 拓展名。

准备工作

在开始之前,请确保您已经成功安装了 VSCode 或 VSCodium。

下载并安装编译套件

常见的编译套件有 MinGW、TDM-GCC等,关于套件的选择请参考知乎:MinGW、MinGW-w64 与TDM-GCC 应该如何选择?。

TDM-GCC 是一个适用于 Windows 的广受好评的编译套件,它包括了稳定更新的 GCC 工具组、一些专为 Windows 设计的补丁和免费且开源的 MinGW 运行时 API。

TDM-GCC 已经将主页迁移至 GitHub Page,你可以在这里下载到它。MinGW 的安装略微复杂,你可以在这里下载到它。

选择适用于系统的版本下载,完成下载后将它安装到你喜欢的位置,本文我们将以安装在D:/Toolkit/TDM-GCC-64/的 TDM-GCC-64 套件为例。如果使用其他套件,在后续配置中一般只需要考虑编译组件所在目录,因为这些套件一般都包含必须的编译组件。

MinGW 要求其安装路径不含空格和中文字符,这也应当是在安装和使用任意程序时都应尽量做到的充分性保障措施。

检查编译组件

来到编译套件的安装目录下(在本文例中为D:/Toolkit/TDM-GCC-64/),我们需要关注的文件有:

文件 名称 描述
./bin/gcc.exe GNU C Compiler GCC 的 C 语言编译器
./bin/g++.exe GNU C**++** Compiler GCC 的 C++ 语言编译器
./bin/gdb.exe GNU Debugger GNU 系统中的标准调试器

居然没有递归定义,这很不 GNU 好吗…

前二者都是编译器,我们将选择 g++.exe。

常见误区:gcc只能编译 C 代码,g++只能编译 C++ 代码。

对于 *.c 文件,gcc 会将它当作 C 代码处理,g++ 会将它当作 C++ 代码处理;对于 *.cpp 文件,二者均会将它当作 C++ 代码处理。

版本问题:gcc32.exe 和 gdb64.exe 的选用

安装了 TDM-GCC 的用户并没有 gcc.exe,应该选用同目录下的 gcc32.exe 作为 Compiler;

要调试 64 位的应用程序(使用 GCC 的 –m64 选项或 IBM XL C/C++ 编译器的 –q64 选项编译),应该选用 gdb64.exe 作为 Debugger。

配置 VSCode 工作区

为你的开发工作创建一个 VSCode Workspace,你的 C++ 开发工作需要在这里展开。下面我们将完成对该工作区的配置。

配置 Extensions

你需要安装以下 Extensions:

  • C/C++:针对 C、C++ 的语言支持

你可以选装(建议只选择一个)以下 Extensions 来提高开发效率:

  • Visual Studio IntelliCode:基于 AI 的语法自动补全。
  • Tabnine Autocomplete AI:另一个更强大的基于 AI 的语法自动补全。
  • C/C++ Snippets

配置 .vscode 目录与配置文件

为了让 VSCode 的编译流程能够正确使用我们安装好的编译套件,我们需要

  • 在工作区下的 .vscode 目录下编写一些配置文件
  • 在工作区下准备一个 debug 目录

工作区目录至少需要具有这样的树状结构:

├─ .vscode/
│ ├─ tasks.json
│ └─ launch.json
├─ debug/
└─…

如果你看不懂这个表达,此即:

创建该目录和目录下的文件。

除手动创建外,你也可以通过 Ctrl + Shift + P 调出命令窗口,通过指令创建。

对于 launch.json 配置文件,你也可以通过 Ctrl + Shift + D 调出 VSCode 左侧的 Run 菜单 来添加。

配置 tasks.json

tasks.json 定义了 VSCode 执行编译、打包、测试、部署等自动化任务的方法,关于它的介绍可以参考 VSCode Docs - Tasks。这里我们通过它完成对代码的编译工作。

和其他 json 一样,该文件用一个 tasks 数组保存一个工作区内的 tasks,其中包含若干个 json 对象,每个对象配置了一个 task。

为该数组追加 2 个 task:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"tasks": [
{
"type": "shell",
"label": "g++ build",
"command": "D:/Toolkit/TDM-GCC-64/bin/g++.exe",
"args": [
"-g",
"${fileDirname}/*.cpp",
"-o",
"${workspaceFolder}/debug/${fileBasenameNoExtension}.exe"
],
"options": {
"cwd": "D:/Toolkit/TDM-GCC-64/bin"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
}
}
],
"version": "2.0.0"
}

这一配置指定 g++ 读取某个目录下的全部 C++ 源代码文件,将编译后的可执行文件生成在工作区的 debug 目录下。如果要编译 C 项目,可在 args 数组中修改。

这一配置可以处理单个源代码文件的项目,也可处理包括多个文件,如多个头文件、源代码文件且有 include 关系的项目。只可惜,如果你的项目目录有子文件夹,这一配置无法将子文件夹中的代码加入编译。你可以在 args 数组中添加这些目录的路径,但这是面向项目,而不是面向工作区的修改。更好的办法是配置 Make 或 CMake 进行编译,此处不展开讲解。

以下对键值对进行说明:

  • label:具有特异性的 task 命名标签,用于标注该 task;
  • type:Task 的类型,这里为 shell 指令;
  • command:上述 shell task 应当执行的命令,这里我们选择 g++ 来编译程序;
  • args:上述 command 的参数列表,是一个对象数组,
    • 上面的参数指定将代码文件所在目录下的所有 C++ 文件编译生成 exe 文件,其中有3个参数拓展语法:
    • ${file} 代表代码文件的绝对路径;
    • ${fileDirname} 代表代码文件的路径所在目录的路径;
    • ${fileBasenameNoExtension} 代表代码文件的文件名;
  • problemMatcher:错误捕获器,这里使用 gcc 捕获错误;
  • group:任务分组,指定此配置为 build 分组中的默认配置。

需要你特别关注并客制化的键值对是:

  • label:填入你喜欢的名字,建议为 g++ build active files;
  • command:填入你的 g++.exe 的相对/绝对路径,建议填写绝对路径以提高可移植性;
  • args:如果有能力,可以自己配置编译选项。

如果有能力与需求,其他选项可以自己配置。

配置 launch.json

launch.json 定义了 VSCode 调试程序的方法,关于它的介绍可以参考 VSCode Docs - Debugging。这里我们通过它完成可执行文件的调试。

该文件和 tasks.json 的大致架构基本一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"version": "0.2.0",
"configurations": [
{
"name": "C++ 编译并调试",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/debug/${fileBasenameNoExtension}.exe",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "D:/Toolkit/TDM-GCC-64/bin/gdb.exe",
"setupCommands": [
{
"description": "为 gdb 启用整齐打印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "g++ build"
},
]
}

这一配置指定运行 g++ build 任务编译项目,然后通过 GDB 调试项目。

以下对键值对进行说明:

  • name:相当于 tasks.json 的 label,是命名标签;
  • type:此配置的类型,这里为 cppdbg,即调试 C++ 程序;
  • preLaunchTask:此配置运行前要执行的任务,我们通过该项先编译再调试;
  • program:需要调试的文件,我们通过该项选择编译出的可执行文件;
  • request:调试挂载方式:
    • launch :VSCode 运行 program 指定的程序并调试;
    • attach :VSCode 将调试工具挂载到已经在运行的程序中。
  • args:参数列表,这里留空;
  • stopAtEntry:是否在程序入口点暂停:
    • true:在程序入口点的第一行打上断点,在 attach 模式下无效;
    • false:不暂停。
  • cwd:Current Working Directory,即当前的工作目录;
  • environment:为程序环境注入的环境变量,是一个 json 数组;
  • externalConsole:是否打开额外的 Console 窗口进行调试:
    • true:是;
    • false:否,调试会在 VSCode 内置的 TERMINAL 中进行。
  • MIMode:VSCode 需要连接的调试器:
    • gdb:连接到 gdb;
    • lldb:连接到 lldb。
  • miDebuggerPath:调试器所在的路径;
  • setupCommands:配置编译器的指令,是一个 json 数组。

需要你特别关注并客制化的键值对是:

  • name:填入你喜欢的名字,建议为 gdb launch;
  • preLaunchTask:填入上面 tasks.json 中输出的可执行文件的路径。
  • miDebuggerPath:填入你的 gdb.exe 的相对/绝对路径,建议填写绝对路径以提高可移植性;
  • args:如果有能力,可以自己配置编译选项;

如果有能力与需求,其他选项可以自己配置。关于键值对的具体说明,可以参考VSCode Docs。

其他配置

如果有需要,还可以参照VSCode Docs - C/C++ Properties配置 c_cpp_properties.json、根据VSCode Docs - User and Workspace Settings配置 settings.json。

更多关于参数拓展语法的资料,可以参考:CSDN 的一篇博客和Bash Hackers Wiki 上的说明。

调试程序

在工作区中编写一个 C 或 C++ 程序,保存,按 F5,或在 Run 菜单中选定刚才配置好的 C++ 编译并调试 选项并按下 Start Debugging 按钮。

如果调试正常进行,则配置完成。

常见问题

我想在其他目录开发!

可以,但由于以上配置是基于工作区的,你需要在新的开发目录下也进行配置。

幸运的是,以上配置(除编译器、调试器外)均采用相对路径和路径拓展语法,你只需要将 .vscode 和 debug 目录拷贝到新的工作区下即可继续工作!

我不想把编译出的可执行文件放在工作区的 debug 目录下!

如果我们把所有的可执行文件堆放在工作区的 debug 目录下,在进行多文件编译的时候,对着 main.cpp 文件(即程序入口点所在处)运行上面的 Launch 配置,每一次编译出来的都是 main.exe,会将其他工程的 main.exe 覆盖掉,导致我们需要直接运行/调试其他工程时需要重新编译。

将可执行文件直接和源代码放在一起

一种解决思路是将可执行文件直接和源代码放在一起。根据上面的说明,聪明的你一定知道应该:

修改 tasks.json 中的配置中的 args 数组中的

1
"${workspaceFolder}/debug/${fileBasenameNoExtension}.exe"

为:

1
"${fileDirname}/${fileBasenameNoExtension}.exe"

并修改 launch.json 中的配置的

1
"program": "${workspaceFolder}/debug/${fileBasenameNoExtension}.exe",

为:

1
"program": "${fileDirname}/${fileBasenameNoExtension}.exe",

这样一来,工作区根目录下的 debug 文件夹也不需要了。

将可执行文件放入源代码文件目录下的 debug 目录

对于喜欢收纳的开发者,我们希望在源代码所在目录下再新建 debug 目录,并将可执行文件存放进去,但若此目录不存在,则运行会报错。我们可以每次手动添加,但这过于麻烦。比较优雅的解决方案是在编译前通过一个 Task 新建该目录。

在 tasks.json 的配置中新增 Task:

1
2
3
4
5
{
"type": "shell",
"label": "mkdir debug",
"command": "mkdir -Force ${fileDirname}/debug/"
}

请注意,在 Windows 环境下,上面的 Task 会调用 PowerShell 执行命令。如果不使用 -Force 但文件夹 debug 存在,则会抛出异常导致运行中止,而 -Force 将会抑制这一指令。

在 Linux 环境下,应该使用:

1
"command": "mkdir -p ${fileDirname}/debug/"

指定 Tasks 运行的先后顺序,在两个编译 Task 中新增以下对象:

1
2
3
"dependsOn": [
"mkdir debug"
]

这一对象制定了在该 Task 运行前,先运行一组指定的 Tasks 作为依赖。这样,就可以在每次运行时先在源代码目录下新增 debug 目录。

报错:undefined reference to std::ios_base::Init::Init()

这应当是一个库链接器问题,也即编译器在 C、C++ 中犯了选择困难症没有选择正确的语言的库进行编译。可以尝试以下方法解决:

  • 在 tasks.json 中将 command 对象末尾的 gcc.exe 换为 g++.exe(如果选择了 gcc 编译);
  • 在 tasks.json 中在 args 数组末尾添加 "-lstdc++"(如果决定选择 gcc 编译 C++ 代码)或"-lc"(如果决定选择 gcc 编译 C 代码);
  • 在代码文件的头文件引用中,将 #include <string.h> 改为 #include <string>。

感谢 @我忘了微信号 反馈这个问题!

无法找到输出到控制台的内容

首先请检查程序是否改变了输入输出流的选项,例如使用了 freopen() 将输出重定向。

MinGW 的 gdb 一般会从内置的 DEBUG CONSOLE(调试窗口) 输出调试信息,而 TDM 的 gdb 一般会从内置的 TERMINAL(终端) 输出调试信息。请在这两个窗口内寻找输出。

常见的问题是:使用 TDM 时,当一个 VSCode 窗体首次运行调试时,可能会出现:TERMINAL 窗口被打开,之后 DEBUG CONSOLE 窗口被打开(如下图)。

此时点击 TERMINAL 即可回到调试界面,看到输出到控制台的内容。

感谢 @EasternLake 反馈这个问题!

博主目前并没有研究否通过配置文件指定输出窗口,如果有同志知晓麻烦联系作者或在下方评论区说明,感谢!

参考资料

  1. Visual Studio Code Docs 关于 Tasks 的说明;
  2. Visual Studio Code Docs 关于 Debugging 的说明;
  3. Bash Hackers Wiki 关于 Parameter Expansion 的说明;
  4. GitHub 上的 microsoft/vscode Repo 的 Issue:tasks.json - cwd property in task objects;