在windwos下使用vscode编写C/C++(使用Clang/LLVM并浅探vscode的工作机理)

发布于:2025-09-02 ⋅ 阅读:(16) ⋅ 点赞:(0)

一、前言

当你在网上搜索如何在vscode中编写C++时,你会得到一堆告诉你安装哪些扩展的文章,但也仅仅如此,它们并不会告诉你如何去使用这些东西,以及他们到底起了哪些作用,这就导致很多人使用时云里雾里的,本篇文章内容就是解决这些问题。
首先我们介绍一下本文会用到的扩展(如果你想编写C++,这些扩展基本够用了):C/C++、Cmake Tools、Code Runner、 CodeLLDB。本文还会用到Clang这个编译器,并在Windwos环境中使用它。
在本文的开始你需要了解C/C++的工具链,如果你不了解,作者在前面写过相关的内容,可以去看看,为了测试你是否了解,这里提出一个问题,如果你能回答出来说明你可以继续了:MSVC和Clang的区别?

参考答案:Clang相当于前端,而LLVM相当于后端,他们同属于LLVM项目,前端与后端解耦,通过统一 IR 实现跨语言、跨平台能力;而MSVC本身就包含的前后端,前端和后端深度集成,专为 Windows 生态设计;其中clang++.exe和clang.exe对标cl.exe,前者是C++编译器,后者是C编译器,他们都是编译器的命令行入口。值得一提的是,Clang也提供了一个支持MSVC风格命令行参数的工具,即clang-cl.exe,其余的都是GCC风格。
在这里插入图片描述

二、配置文件与扩展介绍

众所周知,vscode仅仅是一个文本编辑器,如果没有扩展的支持,它就是个大号的记事本,所以vscode除了文件管理以及文本编辑等基础功能外一切你所能使用的功能都是由扩展支持的,包括运行代码和调试。所以了解扩展是干什么的,怎么用的是至关重要的。

vscode配置文件

vscode的项目配置文件都存放在项目目录下的.vscode文件夹下,其中运行C/C++文件和调试C/C++文件这两个功能分别依赖于task.json和launch.json这两个文件。下面是vscode内置的一些预定义变量:

变量 描述
${用户主页} 用户主文件夹的路径
${workspaceFolder} VS Code 中打开的文件夹的路径
${workspaceFolderBasename} VS Code 中打开的文件夹的名称(不带路径分隔符)
${文件} 当前打开的文件
${fileWorkspaceFolder} 当前打开的文件所属的工作区文件夹
${relativeFile} 当前打开的文件相对于 workspaceFolder 的路径
${relativeFileDirname} 当前打开的文件的目录名(相对于 workspaceFolder
${fileBasename} 当前打开的文件的基名称(含扩展名)
${fileBasenameNoExtension} 当前打开的文件的基名称(不含扩展名)
${fileExtname} 当前打开的文件的扩展名
${fileDirname} 当前打开的文件的文件夹路径
${fileDirnameBasename} 当前打开的文件的文件夹名称
${cwd} 启动 VS Code 时任务运行程序的当前工作目录
${lineNumber} 活动文件中当前选定的行号
${columnNumber} 活动文件中当前选定的列号
${selectedText} 活动文件中当前选定的文本
${execPath} 正在运行的 VS Code 可执行文件的路径
${defaultBuildTask} 默认构建任务的名称
${路径分隔符} 操作系统用于分隔文件路径组件的字符(如 \/
${/} ${路径分隔符} 的简写

task.json

定义项目的构建、编译、测试等任务,通俗的说就是这个文件是掌管运行代码的,下面是一个常见的配置:

    "tasks": [
        {
            "type": "cppbuild",//表明这是一个编译任务
            "label": "C/C++: clang++.exe 生成活动文件",
            "command": "D:/Scoop/apps/llvm/current/bin/clang++.exe",
            "args": [
                "-std=c++20",
                "-fcolor-diagnostics",
                "-fansi-escape-codes",
                "-g",
                "${file}",
                "-o",
                "${fileDirname}\\${fileBasenameNoExtension}.exe"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "调试器生成的任务。"
        }
]

其中最重要的是command和args,这两个参数分别定义了编译器的路径以及编译器后面的命令行参数,也就是说这实际上就是相当于在命令行中运行编译命令。当我们第一次运行代码时,通常都会弹出这个界面,这个界面是生成task.json这个配置文件的(由vscode根据你所选的编译器基于内置的模板或者扩展自动生成),如果我们想要每次点击右上角的运行C/C++文件时,可以将"isDefault": true这个值改成false,如果设置成true就代表这个配置文件就是默认的任务配置文件。
在这里插入图片描述

launch.json

文件用于在 Visual Studio Code 中配置调试器,即下面这个窗口:
在这里插入图片描述
下面是一个常见的配置:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) 启动",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/main.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "/path/to/gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "将反汇编风格设置为 Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ]
        },
        {
            "name": "C/C++: clang++.exe 构建和调试活动文件",
            "type": "cppvsdbg",
            "request": "launch",
            "program": "${fileDirname}\\${fileBasenameNoExtension}.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": true,
            "preLaunchTask": "C/C++: clang++.exe 生成活动文件"
        },
}

可以看出根据调试器的配置类型(type字段)不同,其需求的参数也不同,C++常见的调试类型包括cppdbg和cppvsdbg以及llvm三种,其中llvm来自于codelldb这个扩展,codelldb在安装后可能会自己安装一个lldb,安装路径通常在~.vscode\extensions\vadimcn.vscode-lldb-1.11.5\lldb\下,但是在windwos上可能会存在Debug adapter exit code=3221225477, signal=null.的报错,以及其他多种报错,所以我们只能使用cppvsdbg和cppdbg。cppvsdbg(Windows启动)专门用于 Windows 环境,它依赖于 Visual Studio 调试器,而cppdbg(GDB/LLDB启动)具有更好的跨平台性,可用于 Linux、macOS 等非 Windows 平台,也能在 Windows 上使用,通常与 GDB(GNU Debugger)或 LLDB(Low - Level Debugger)等调试器配合使用。但是这里存在一个问题,cppdbg调试适配器仅支持支持 MI 的调试器,而从 10.x 开始,LLVM 工具链已停止lldb-mi.exe随lldb.exe的安装,因此在新版本中,我们也无法使用,所以在windows下我们最好使用cppvsdbg进行调试,甚至可以把代码直接扔进宇宙最好的IDE进行调试。
值得注意的是,配置文件中的顺序就是调试下拉框中显示的顺序:
在这里插入图片描述
这里还要说的一个参数就是"externalConsole": true这个参数,这个参数目前已经弃用,但当其为true时仍可使用,但是vscode的官方文档并没有更新这一点,现在已经等价替换成"console": “externalTerminal”,这个键一共有四个可选值,分别是internalConsole(vscode内置调试控制台,弱交互)、integratedTerminal(vscode内置终端,强交互)、externalTerminal(系统终端,强交互)、newExternalWindow(系统终端,每次新建窗口,强交互)。
最后还有一个参数就是"preLaunchTask": “C/C++: clang++.exe 生成活动文件"这个参数,这个参数是用于引用task任务的,也就是task.json文件中的这一条"label”: “C/C++: clang++.exe 生成活动文件”。
这个机制是因为在 VS Code 中,调试器和编译器的角色是分开的,但它们的配置可能会相互关联,在 VS Code 的默认配置中,调试器(如 LLDB 或 GDB)本身不负责编译代码,而是依赖于预先生成的可执行文件。但 VS Code 提供了一种便利功能:

  • 自动触发编译:当你启动调试会话时,如果检测到可执行文件不存在或已过期,VS Code 会自动触发编译任务。
  • 调试配置中的preLaunchTask:调试配置(launch.json)通常会指定一个预启动任务(如编译),确保调试前代码已正确编译。

settings.json

默认路径在:%APPDATA%\Code。这个文件的作用是覆盖全局设置,定义项目特有的编辑器行为、语言配置等。值得一提的是全局设置中是由另一个settings.json进行管理。下图的Run Code功能依赖于这个设置,而这个功能由Code Runner这个插件提供。
在这里插入图片描述
下面是一些比较重要的配置:

    "code-runner.executorMap": {
        "javascript": "node",
        "java": "cd $dir && javac $fileName && java $fileNameWithoutExt",
        "c": "cd $dir && clang -std=c11 $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        "zig": "zig run",
        "cpp": "cd $dir && clang++ -std=c++20 $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        "objective-c": "cd $dir && gcc -framework Cocoa $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        "php": "php",
        "python": "set PYTHONIOENCODING=utf8 && python -u",
        "perl": "perl",
        "perl6": "perl6",
        "ruby": "ruby",
        "go": "go run",
        "lua": "lua",
        "groovy": "groovy",
        "powershell": "powershell -ExecutionPolicy ByPass -File",
        "bat": "cmd /c",
        "shellscript": "bash",
        "fsharp": "fsi",
        "csharp": "scriptcs",
        "vbscript": "cscript //Nologo",
        "typescript": "ts-node",
        "coffeescript": "coffee",
        "scala": "scala",
        "swift": "swift",
        "julia": "julia",
        "crystal": "crystal",
        "ocaml": "ocaml",
        "r": "Rscript",
        "applescript": "osascript",
        "clojure": "lein exec",
        "haxe": "haxe --cwd $dirWithoutTrailingSlash --run $fileNameWithoutExt",
        "rust": "cd $dir && rustc $fileName && $dir$fileNameWithoutExt",
        "racket": "racket",
        "scheme": "csi -script",
        "ahk": "autohotkey",
        "autoit": "autoit3",
        "dart": "dart",
        "pascal": "cd $dir && fpc $fileName && $dir$fileNameWithoutExt",
        "d": "cd $dir && dmd $fileName && $dir$fileNameWithoutExt",
        "haskell": "runghc",
        "nim": "nim compile --verbosity:0 --hints:off --run",
        "lisp": "sbcl --script",
        "kit": "kitc --run",
        "v": "v run",
        "sass": "sass --style expanded",
        "scss": "scss --style expanded",
        "less": "cd $dir && lessc $fileName $fileNameWithoutExt.css",
        "FortranFreeForm": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        "fortran-modern": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        "fortran_fixed-form": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        "fortran": "cd $dir && gfortran $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",
        "sml": "cd $dir && sml $fileName",
        "mojo": "mojo run",
        "erlang": "escript",
        "spwn": "spwn build",
        "pkl": "cd $dir && pkl eval -f yaml $fileName -o $fileNameWithoutExt.yaml",
        "gleam": "gleam run -m $fileNameWithoutExt"
    },

当我们点击Run Code时,就会根据上面的配置执行相应的命令,值得一提的时,这个配置对于C/C++并没有携带执行标准的信息且默认使用GCC,因此当你想使用一些新特性或者其他编译器时,需要修改这些命令,例如:

-std=c++20//GCC风格参数
/std:c++20//MSVC风格参数

这里额外提一点,如果你使用它运行Python时出现乱码,请在Python那一行的命令中添加下面的命令:

set PYTHONIOENCODING=utf8

c_cpp_properties.json

这个文件主要是配置C/C++ 语言服务(IntelliSense)的编译器路径、包含目录、编译选项等。来自C/C++这个扩展,并不影响实际的编译和调试的过程。

vscode扩展

默认路径在:%USERPROFILE%.vscode\extensions。

C/C++

这个扩展主要是提供语法高亮、IntelliSense、调试器支持等基础功能。c_cpp_properties.json是其文本版的配置文件(它还有个UI版的可以在设置中找到):

{
    "configurations": [
        {
            "name": "Win32",
            "includePath": [
                "${workspaceFolder}/**",
                "${vcpkgRoot}/x64-windows/include"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE"
            ],
            "windowsSdkVersion": "10.0.26100.0",
            "intelliSenseMode": "windows-clang-x64",
            "compilerPath": "D:/Scoop/apps/llvm/current/bin/clang++.exe",
            "cppStandard": "c++23",
            "cStandard": "c11"
        }
    ],
    "version": 4
}

Cmake Tools

这个扩展主要用于 CMake 项目的自动构建、配置和管理。

Code Runner

这个扩展主要用于快速运行代码。

CodeLLDB

这个扩展主要用于使用lldb进行调试。

三、构建系统

构建系统就是用于管理编译过程的,C++目前有三大构建系统,分别是Make/GNU Make(这是最经典的构建系统,基于Makefile文件定义构建规则,通过目标、依赖和命令描述构建流程)、MSBuild(由微软开发的构建系统,基于XML格式的.csproj/.vcxproj配置文件,是vsstudio的默认构建引擎)、Ninja(轻量级、高性能的构建系统,专注于快速执行构建,比Make快,设计目标是被机器生成而非手写,增量构建效率高)。而Cmake是一种C++事实上的标准构建系统生成工具,注意是生成工具而非构建系统,也就是说我们可以通过Cmake去生成上面的三种构建系统的配置文件,我们下面就来讲解一下Cmake。
首先在新版本的Cmake中,Cmake最重要的文件已经由原本的CMakeLists.txt拆分成了两个文件,即CMakeLists.txt和CMakePresets.json,CMakeLists.txt用于定义项目的编译规则、依赖关系和目标结构(如可执行文件、库),下面是一个CMakeLists.txt文件的示例,包含了一些C++项目应该包含的东西:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.12)
project(MyProject LANGUAGES CXX)

# 设置C++标准
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 添加可执行文件
add_executable(my_app src/main.cpp src/utils.cpp)

# 添加库
add_library(my_lib STATIC src/mylib.cpp)

# 链接库
target_link_libraries(my_app PRIVATE my_lib)

# 添加编译选项
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
    target_compile_options(my_app PRIVATE -Wall -Wextra -Wpedantic)
endif()

CMakePresets.json定义预配置的构建环境,简化 CMake 命令行参数,configurePresets:定义配置阶段参数(如编译器、构建目录),buildPresets:定义构建阶段参数(如构建类型、并行度),testPresets:定义测试阶段参数,下面是一个CMakePresets.json的示例:

{
  "version": 3,
  "cmakeMinimumRequired": {
    "major": 3,
    "minor": 20,
    "patch": 0
  },
  "configurePresets": [
    {
      "name": "windows-msvc-debug",
      "displayName": "Windows MSVC Debug",
      "description": "使用MSVC编译器在Windows上进行调试构建",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build/windows-msvc-debug",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Debug",
        "CMAKE_C_COMPILER": "cl.exe",
        "CMAKE_CXX_COMPILER": "cl.exe",
        "CMAKE_INSTALL_PREFIX": "${sourceDir}/install/windows-msvc-debug"
      },
      "environment": {
        "PATH": ["${env:PATH}", "C:/custom/tools"]
      }
    },
    {
      "name": "linux-clang-release",
      "displayName": "Linux Clang Release",
      "description": "使用Clang编译器在Linux上进行发布构建",
      "generator": "Ninja",
      "binaryDir": "${sourceDir}/build/linux-clang-release",
      "cacheVariables": {
        "CMAKE_BUILD_TYPE": "Release",
        "CMAKE_C_COMPILER": "clang",
        "CMAKE_CXX_COMPILER": "clang++",
        "CMAKE_INSTALL_PREFIX": "${sourceDir}/install/linux-clang-release",
        "CMAKE_CXX_FLAGS": "-O3 -march=native"
      }
    }
  ],
  "buildPresets": [
    {
      "name": "windows-build-debug",
      "displayName": "Windows Debug Build",
      "configurePreset": "windows-msvc-debug",
      "jobs": 8,
      "targets": ["all"],
      "configuration": "Debug"
    },
    {
      "name": "linux-build-release",
      "displayName": "Linux Release Build",
      "configurePreset": "linux-clang-release",
      "jobs": "auto",
      "targets": ["install"],
      "configuration": "Release"
    }
  ],
  "testPresets": [
    {
      "name": "windows-test-debug",
      "displayName": "Windows Debug Test",
      "configurePreset": "windows-msvc-debug",
      "output": {
        "outputOnFailure": true,
        "showOnlyFailed": false
      },
      "passEnvironment": ["PATH"],
      "ctestArgs": ["--timeout", "300", "-j", "4"]
    },
    {
      "name": "linux-test-release",
      "displayName": "Linux Release Test",
      "configurePreset": "linux-clang-release",
      "output": {
        "outputOnFailure": true,
        "showOnlyFailed": false,
        "outputFormat": "json-v1"
      },
      "environment": {
        "LD_LIBRARY_PATH": "${env:LD_LIBRARY_PATH}:${sourceDir}/build/linux-clang-release/lib"
      },
      "ctestArgs": ["--output-junit", "test-results.xml", "-j", "8"]
    }
  ]
}

值得一提的是CMakePresets.json还分出了一个CMakeUserPresets.json,CMakePresets.json定义项目的标准构建配置,由项目维护者创建并提交到版本控制系统,CMakeUserPresets.json定义开发者的个人定制配置,不提交到版本控制系统,实际使用时先读取CMakePresets.json,再读取CMakeUserPresets.json,并覆盖同名预设。

四、编译和调试

由于我们平时调试代码时都是在源代码中一行一行的调试,所以可能会让人产生误解,即调试的对象是cpp源代码文件,但实际上调试的东西是编译出来的exe文件,编译器将源代码转换成可执行文件,然后调试器加载并分析可执行文件,提供断点、变量查看等功能。
在VScode中当你安装了前面提到的插件后,在vscode你将能够找到四种方式去运行你的代码:
右上角内置提供的调试和运行文件以及来自Code Runner插件提供的Run Code功能
在这里插入图片描述
来自Code Runner插件提供的Run Code功能我们在前面以及讲解过了,这里我们主要是讲解一下右上角内置提供的调试和运行文件,这个东西依赖于launch.json,当我们点击运行C/C++文件时它会调用内置的调试器去间接的运行exe文件,而这个过程本质上是个调试任务,所以在vscode中是没有直接运行exe文件的选项的,从我们点击运行C/C++文件时一闪而过的命令就能看出来,这个命令和调试C/C++文件时运行的命令时一闪而过的命令是一样的,因此这个功能中的运行二字的全称在vscode中应该叫以非调试模式运行。而task.json是通过"preLaunchTask": "C/C++: clang++.exe 生成活动文件"这个参数来调用的,也就是说当我们不设置这个参数或者设置为null,我们运行或者调试C/C++文件时并不会启动编译任务,当我们设置时,则会每次启动都会调用这个编译任务。
在这里插入图片描述
但是,我们可以通过在task.json文件中定义任务来单独运行exe文件,因为vscode在命令面板中提供了单独使用task.json运行程序的内容,即运行生成任务(“group”: “build”),运行测试任务(“group”: “test”)以及运行任务功能(任意选择任务),例如我们设置如下的任务:

        {
            "type": "shell",
            "label": "C/C++: clang++.exe 生成活动文件123",
            "command": "D:/Scoop/apps/llvm/current/bin/clang++.exe",
            "args": [
                "-std=c++20",
                "-fcolor-diagnostics",
                "-fansi-escape-codes",
                "-g",
                "${file}",
                "-o",
                "${fileDirname}\\${fileBasenameNoExtension}.exe"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": false
            },
            "detail": "调试器生成的任务123。"
        },
        {
            "label": "run",
            "type": "shell",
            "command": "${fileDirname}\\${fileBasenameNoExtension}.exe",
            "group": "test",
            "dependsOn": ["C/C++: clang++.exe 生成活动文件123"]
        },

我们就可以通过vscode提供的功能单独选择运行某一个任务来达到我们的目的。这里值得一提的是"dependsOn": [“C/C++: clang++.exe 生成活动文件123”]这个键值对,这个表明此任务依赖于label为C/C++: clang++.exe 生成活动文件123的任务,即执行此任务前会先执行所依赖的任务,如果想单独运行exe文件去掉这一项即可,这样我们就能摆脱对调试器的依赖了。
左上角内置提供的调试监控界面
在这里插入图片描述
底部由CmakeTools提供的基于Cmake的运行和调试功能
在这里插入图片描述

五、总结

本文到此结束了,现在你应该了解了一个C/C++文件是如何在vscode中跑起来的。


网站公告

今日签到

点亮在社区的每一天
去签到