ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

VSCode写C语言:先装编译器与调试器,再配置插件和JSON

VSCode写C语言:先装编译器与调试器,再配置插件和JSON 装机这件事最容易走反的一步就是先装编辑器。我见过太多刚学 C 语言的人第一件事是打开搜索框输入 VSCode 下载装完之后对着一个空白窗口发愣然后随手在扩展商店里搜C一口气装了七八个插件结果写出来的第一个程序要么编译不了要么 printf 出来的中文变成一串问号最后得出结论这编辑器不好用还是回去用老 IDE 吧。问题从来不在 VSCode而在于编辑器、编译器、调试器这三件事被混成了一件。这篇内容就是把这个顺序捋直先落地编译器和调试器再谈插件最后才是那四个 JSON 配置文件。适合完全没接触过命令行、正在学 C 语言基础语法、准备跟着公开课做练习题的人也适合装过 VSCode 但一直没搞明白为什么跑不起来的人。1. 装VSCode写C的顺序八成的人一开始就搞反了1.1 编辑器、编译器、调试器三件被混在一起的事很多人默认装了 VSCode 就能写 C这个认知偏差是后面所有麻烦的源头。VSCode 本身只是个文本编辑器加外壳它连新建一个 .c 文件然后变成可执行程序这件事都做不到——真正干活的是一套独立安装在系统里的命令行工具链。拆开看就是三层编译器负责把 .c 源码翻译成机器指令C 语言这边通常就是 gcc调试器负责让程序在断点处停下来让你看到变量的值C 语言这边通常是 gdb编辑器只负责让你打字舒服提供补全、高亮、跳转这些体验。VSCode 的插件做的事情本质上只是把编辑器和外面那套命令行工具对接起来它自己不会编译任何代码。这个区别听起来像抠字眼但直接影响排查思路。当你的程序跑不起来先想的是gcc 在不在 PATH 里而不是我是不是插件装少了——九成的编译不了都出在前者。反过来当你写代码时没有补全提示、跳转不到函数定义那才轮到插件的问题。把故障域先分清楚后面每一步都能省下大量时间。我自己的习惯是装完系统第一件事就是打开命令行敲gcc --version能出版本号才去下载 VSCode。这个顺序反过来也行但心里那根先工具链后编辑器的弦不能松。1.2 什么情况下先用IDE更省时间说句实在话不是所有人都适合一上来就用 VSCode 学 C。如果你的目标是两周内把指针、数组、函数这些语法过一遍做题为主那集成开发环境反而更顺手装完就有编译器点一下按钮就跑不用碰任何配置文件。VSCode 的优势在哪里在于它逼着你理解构建过程。tasks.json 里你要自己写 gcc 的参数launch.json 里你要自己指定 gdb 的路径这些配置写一遍你对源码是怎么变成可执行文件的这件事的理解会比点十年绿色三角按钮的人深得多。等你后面要处理多文件工程、要引入第三方库、要看懂别人项目里的 Makefile这份理解就是本钱。所以我的建议是分两种情况如果只是想快速把语法过一遍先随便用个能跑的环境别卡在配置上如果是打算长期用 C 语言或者要跟着课程做几十道练习题那就忍过配置这一关一次性把 VSCode 调好后面全是收益。这个判断做在前面比装完再纠结要不要换回来强。2. 工具链落地从编译器到PATH的完整链路2.1 MinGW-w64的几个来源怎么挑Windows 上没有原生的 gcc需要装一套移植版本目前主流选择是 MinGW-w64 这个分支。获取渠道我实际用过三个各有各的适用场景。来源特点适合谁独立解压包w64devkit 类单文件解压即用自带 gcc、gdb、make想快速跑起来、不想装包管理器的人MSYS2用 pacman 管理升级方便生态全以后要装第三方库、想长期维护环境的人各类预编译发行版版本新、参数选项多需要特定 gcc 版本的人我一般推荐第一条路。理由很简单初学者最怕的是安装过程本身出岔子而解压包没有安装过程解压完就能用出问题的面小得多。MSYS2 更规范但它本身是个类 Unix 环境pacman 的命令行操作对刚学 C 的人是额外负担一旦装错包名排查起来又是一轮折腾。版本上不用追新gcc 的 C11 支持早就稳定了选个主流版本就行。真正要确认的是这个包里有没有 gdb——有些精简包只带编译器不带调试器等你想打断点的时候才发现缺东西又得重新下一遍。2.2 解压路径与PATH设置两个最常见的坑解压路径这件事我专门拿出来说因为踩过。路径里不要有中文不要有空格。像C:\Program Files\...这种带空格的路径在某些脚本环境下会被拆成两段导致命令莫名其妙地失败中文路径则会在 gdb 读取调试信息时出编码问题报错信息还特别难看懂。我自己的路径习惯是D:\Dev\mingw64短、全英文、无空格一眼就知道是什么。接下来是 PATH。这一步的本质是告诉系统当我在命令行敲 gcc 的时候去哪个文件夹找这个程序。需要加进 PATH 的不是 mingw64 根目录而是它下面的bin目录因为可执行文件都在那里。操作路径是系统属性 → 高级 → 环境变量 → 在用户变量里找到 Path → 编辑 → 新建 → 粘贴你的 bin 目录完整路径。注意是新建一条不是把它追加到某条已有记录的末尾这两者的区别在后续排查时很明显——单独一条删起来干净。提示改完 PATH 之后已经打开的终端窗口不会生效必须关掉重开。这个细节坑过无数人改完发现还是提示找不到命令第一反应应该是我重开终端了吗。2.3 验证安装成功的三条命令配置完不要急着开 VSCode先在命令行把这三条命令挨个敲一遍全部有输出才算过关。gcc --version gdb --version mingw32-make --version第一条输出编译器版本第二条输出调试器版本第三条输出 make 的版本有些发行版里这个命令直接叫make。三条都有版本号说明 PATH 配对了工具链齐了。如果某一条提示不是内部或外部命令回到上一步检查 PATH如果 gcc 有输出而 gdb 没有说明你下载的是不带调试器的包换一个完整的。这三条命令花不了一分钟但能把后面百分之八十的诡异问题挡在门外。顺便提一句命令行的选择上用系统自带的 PowerShell 或者 CMD 都行关键是记住你用的是哪一个因为后面 VSCode 的终端配置要和它保持一致。混着用会导致我明明配好了但 VSCode 里不行这种问题。3. 插件只装这七个每个解决什么问题3.1 C/C与Extension Pack先分清它们的关系扩展商店里搜 C第一个跳出来的通常是微软官方的 C/C 扩展再往下还有一个叫 Extension Pack 的。很多人两个都装其实没必要。C/C 扩展是主体它提供三样东西语法高亮和智能提示IntelliSense、代码跳转和符号查找、调试支持对接 gdb。对初学者来说这三个功能就是全部所需。Extension Pack 是把 C/C 扩展和另外几个主题、辅助插件打了个包装了它等于把 C/C 也一起装了。如果你已经单独装了 C/C再装 Pack 只是多几个用不上的主题占地方还让插件列表变乱。我的建议是只装 C/C 这一个。至于怎么装左侧活动栏点扩展图标搜索框输入C/C认准发布者是 Microsoft 的那个点安装。装完不用重启但建议重开一次窗口让语言服务完整加载。判断它有没有生效有个很直观的方法新建一个 .c 文件敲#include stdio.h如果 stdio.h 变蓝了、并且鼠标悬停在 printf 上能看到函数原型提示说明 IntelliSense 起来了。3.2 Code Runner省事的代价是吃掉标准输入Code Runner 是初学者最爱装的插件之一因为它给了你一个右上角的三角按钮点一下就跑。这个便利性确实香但它有个默认行为必须改否则你会在第一个用到 scanf 的程序上卡住。问题出在 Code Runner 默认把程序输出放在输出面板里而那个面板只是个只读的文本显示区不接受键盘输入。你的 scanf 在那里等输入但你敲的字根本进不去程序就那么挂着看起来像死循环。很多人第一次遇到这个现象会以为是自己的 scanf 用错了在那反复改代码其实代码一点问题没有。改法是在设置里把输出切到终端{ code-runner.runInTerminal: true, code-runner.saveFileBeforeRun: true, code-runner.clearPreviousOutput: true }第一行让它把程序放进真正的终端里跑终端是可以输入的第二行保证每次运行前保存文件避免改了代码但跑的还是旧版本这种幻觉第三行清掉上一次的输出不然多次运行的结果会叠在一起看着眼晕。还有一点要清楚Code Runner 本质上就是帮你执行了一条命令它不会给你调试功能。想看变量、想单步走还得走后面第 6 节的调试配置。3.3 让代码看得顺眼的几个小插件除了核心功能有几个小插件对新手体验的提升很明显占用也不大。Error Lens把错误信息直接显示在出错那一行的末尾不用再去看底部的问题面板。初学阶段最常见的就是少个分号、括号不配对这个插件能让你在眼皮底下看到问题改起来快很多。它不改变编译行为纯粹是显示层的增强。Better C Syntax提供更细的语法着色比如把类型名、函数名、宏用不同颜色区分开。视觉上舒服也能帮你更快建立这个词是类型还是变量的直觉。中文语言包让界面汉化英文界面不熟悉的话装上能省不少查词时间。不过我得说一句菜单汉化了但编译器的报错还是英文所以别指望靠汉化绕开英文报错这一关该认得认。图标主题属于纯装饰让文件列表里 .c 文件和 .h 文件有不同的图标找文件快一点。装不装都行。3.4 clangd不是必需品什么时候才该上网上有些教程会推荐用 clangd 替代微软的 C/C 扩展做代码补全。我的看法是初学阶段别碰。clangd 的优势在于补全速度和准确性但它需要额外的配置——它要读懂你的编译命令才能知道头文件在哪、用了什么标准。这个信息的来源是compile_commands.json对单文件的小程序来说你要么手工写一个要么装别的工具生成。而且它和微软的 C/C 扩展在补全功能上是冲突的两个同时开着提示会打架。什么时候值得上当你开始维护多文件工程、文件数超过十几个、微软那个扩展的补全开始明显卡顿的时候再来考虑。那时候你已经能看懂构建命令了配 clangd 也不算负担。现阶段先把能跑、能调这两件事做扎实。4. 四个JSON配置文件逐步写一遍4.1 c_cpp_properties.json告诉插件去哪儿找头文件这个文件不参与编译它只服务于编辑器的智能提示。也就是说它写错了你的程序还是能编译但补全会失效、跳转会失败。这个定位先搞清楚排查的时候才能分清是编辑体验的问题还是构建的问题。生成方式按CtrlShiftP打开命令面板输入C/C: Edit Configurations (JSON)回车VSCode 会在项目目录下创建.vscode/c_cpp_properties.json。{ version: 4, configurations: [ { name: Win32, includePath: [ ${workspaceFolder}/** ], defines: [ _DEBUG, UNICODE ], compilerPath: D:\\Dev\\mingw64\\bin\\gcc.exe, cStandard: c11, cppStandard: c17, intelliSenseMode: windows-gcc-x64 } ] }几个字段值得单独说。compilerPath是最关键的一行指向你的 gcc.exe插件会去问这个编译器你的默认头文件搜索路径有哪些所以这一行填对了下面大部分的包含路径都不用自己操心。intelliSenseMode要和你的工具链匹配Windows 上的 64 位 gcc 就是windows-gcc-x64这个选错会导致有些标准库头文件解析不出来。cStandard填 c11 就够现在主流的教材和课程都还在这个标准范围内。注意 JSON 里的路径反斜杠要写成双反斜杠因为单反斜杠在 JSON 里是转义字符。这个细节记一下能避免不少配置明明写了却不生效的困惑。4.2 tasks.json把编译命令固化成任务tasks.json 的作用是把一长串编译命令收进一个名字里之后按CtrlShiftB就执行。没有它你得每次在终端里手动敲gcc -g -Wall -stdc11 main.c -o main.exe敲错一个参数就白等。生成方式命令面板输入Tasks: Configure Task→ 选从模板创建 → 选Others然后改成下面这样{ version: 2.0.0, tasks: [ { label: build-c, type: shell, command: gcc, args: [ -g, -Wall, -Wextra, -stdc11, ${file}, -o, ${fileDirname}\\${fileBasenameNoExtension}.exe ], group: { kind: build, isDefault: true }, problemMatcher: [$gcc], presentation: { reveal: always, panel: shared, clear: true } } ] }参数逐个解释因为这几个是你以后每天都要用的。-g生成调试信息没有它 gdb 打不了断点这一条千万不能省。-Wall打开常用警告-Wextra是更严的一档。C 语言里有些错误编译器默认不报比如赋值和比较写混了、变量定义了没用加上这两条能给你提前抓出来。我见过太多人因为没开警告把一个赋值错误查了半小时。-stdc11明确标准版本避免不同编译器默认值不一致导致的怪异行为。${file}表示当前打开的文件${fileDirname}是它所在目录${fileBasenameNoExtension}是去掉扩展名的文件名。用.exe后缀是因为 Windows 上执行习惯如此其实不加也能跑。problemMatcher填$gcc之后编译错误会以可点击的形式列出来点一下跳到对应行比在终端里对着滚动的红字找位置强太多。presentation里的clear: true每次清屏输出干净。4.3 launch.json让断点真的停得下来这个文件管调试。生成方式左侧点运行和调试图标点创建 launch.json 文件环境选 C (GDB/LLDB)然后改成{ version: 0.2.0, configurations: [ { name: gdb-debug, type: cppdbg, request: launch, program: ${fileDirname}\\${fileBasenameNoExtension}.exe, args: [], stopAtEntry: false, cwd: ${fileDirname}, environment: [], externalConsole: false, MIMode: gdb, miDebuggerPath: D:\\Dev\\mingw64\\bin\\gdb.exe, preLaunchTask: build-c, setupCommands: [ { description: 启用美化输出, text: -enable-pretty-printing, ignoreFailures: true } ] } ] }miDebuggerPath是必须自己改的一行指向 gdb.exe。填错或者不填点调试会报找不到调试器这是新手最常见的调试报错。program要和 tasks.json 里的输出路径对上否则会出现程序已修改但调试的还是旧版本。preLaunchTask填build-c这个值和 tasks.json 里的label一致作用是每次调试前自动重新编译。这一条非常关键不然你会调试一个根本没包含最新修改的可执行文件看着变量值不对怀疑人生。externalConsole设成 false 表示用 VSCode 内置终端好处是不弹额外的黑窗口坏处是某些需要窗口交互的程序跑不了不过学基础语法阶段碰不到。4.4 settings.json几行字解决输入与编码工作区的 settings.json 里放这些{ files.encoding: utf8, files.autoGuessEncoding: false, editor.tabSize: 4, editor.insertSpaces: true, editor.formatOnSave: false, code-runner.runInTerminal: true, code-runner.executorMap: { c: cd $dir chcp 65001 nul gcc -stdc11 -Wall -g $fileName -o $fileNameWithoutExt.exe .\\$fileNameWithoutExt.exe } }files.encoding统一 UTF-8这是后面解决中文乱码的基础。editor.tabSize设成 4 是因为 C 语言的代码缩进惯例是 4 个空格用 Tab 的话在不同编辑器里显示宽度不一样代码贴给别人看会乱。formatOnSave我建议先关掉初学阶段自动格式化有时会把你的缩进习惯改掉反而看不清自己写的逻辑结构。executorMap里的那行命令前面加了chcp 65001这是把终端代码页切换成 UTF-8。为什么要加这一句下一节详细讲。配置文件都写完之后建议把.vscode这个文件夹保存下来或者丢进你自己的模板仓库。新开一个练习项目的时候直接复制过去改一改路径就行比每次重配省事得多。5. 乱码、卡死、找不到文件一条完整的排查链5.1 中文输出变问号的两种根因这是中文用户遇到频率最高的问题printf 里写的中文运行时输出成一串问号或者方块。原因分两种处理方式不一样。第一种是源文件的编码问题。文件本身存的不是 UTF-8可能是 GBK。VSCode 右下角状态栏会显示当前编码点一下可以切换。如果显示 GBK选通过编码保存改成 UTF-8。这个改完文件内容本身统一了但终端可能还有问题看第二种。第二种是终端代码页不匹配。Windows 命令行默认用 GBK代码页 936而你的源文件是 UTF-8 存的程序输出的 UTF-8 字节流被终端按 GBK 去解释自然就乱了。解决办法就是前面 settings.json 里那句chcp 65001把终端切成 UTF-8。不过要说清楚chcp 65001只对你手动跑的那次终端会话有效。如果你用 Code Runner 跑程序得写进executorMap如果你用 F5 调试跑终端是调试器起的那一句不一定生效需要在 launch.json 之外单独处理或者干脆接受调试时用英文输出的妥协。我的实际做法是代码里的调试输出优先用英文需要用中文的地方比如最后交作业单独验一遍终端编码。这样既不耽误调试效率也避免了编码问题的反复折腾。这个取舍看起来不优雅但比花两小时查编码要值。5.2 scanf之后程序像卡住其实是终端选错了前面提过 Code Runner 的输出面板不接受输入这里展开说怎么判断。现象是程序编译通过运行时前面该打印的都打印了然后光标不动也没有任何提示等多久都没反应。你反复检查 scanf 的格式字符串觉得没问题。判断方法很简单把code-runner.runInTerminal改成 true重跑。如果这时候能正常输入了那就确认是输出面板的问题。如果改成终端还是不行那就是代码本身的问题得看下一段。代码层面的坑主要是这几种。scanf(%d, n)少写了取地址符程序会试图往一个错误的内存地址写数据直接崩溃或者行为诡异编译时开了-Wall会给你一个警告这也是我坚持开警告的原因之一。scanf(%d\n, n)在格式串末尾加了\n会导致 scanf 一直等你输入下一个非空白字符才返回看起来就像卡住了这个坑非常隐蔽初学者几乎都踩过。还有scanf处理字符串时没用宽度限制输入超长内容会越界这个属于安全习惯一开始就养成比较好。排查这类问题的顺序我总结成一个固定动作先在命令行手动跑一遍。如果命令行能跑 VSCode 里不行问题在编辑器配置如果命令行也不行问题在代码。这一步能把排查范围直接砍一半。5.3 编译报错的第一句怎么读编译器的报错经常一次刷出几十行新手容易慌。实际上九成的报错第一行就说明白了后面的都是连锁反应。expected ; before } token这类就是某一行少了分号位置通常在你改动的附近。xxx undeclared说明这个变量没定义就用了或者头文件没包含。最需要单独说的是undefined reference to xxx——这个和上面的未声明完全是两回事。未声明是编译阶段的问题编译器压根不知道这个名字是什么。未定义引用是链接阶段的问题意思是编译器知道这个名字你声明过了但去找它的实现时没找到。多文件工程里出现这个报错八成是你编译命令里漏了一个 .c 文件单文件里出现通常是函数名拼错但声明写对了或者你调了一个需要额外链接库的函数忘了加-l参数。这两类报错的应对方式截然不同把编译错误和链接错误这两个概念分开你就跨过了初学者到进阶的一道门槛。我建议每次遇到报错先在心里给这句话归个类再决定是去改代码还是去改编译命令。6. 从能跑起来到调得动断点、指针与多文件6.1 GDB调试的起手式程序能编译能运行之后下一步是学会调试。为什么这个技能必须早点掌握因为当你的程序输出结果不对靠 printf 到处打印变量值来排查效率会随着代码变长断崖式下降。断点调试能让你在程序运行的任意时刻一次性看到所有变量的值。起手流程是这样的在你想观察的那一行代码左边的行号区域点一下出现一个红点这就是断点。然后按 F5前提是 launch.json 配好了。程序会运行到断点处停下此时左侧出现变量面板当前作用域里所有局部变量和它们的值都在上面。上方工具栏有继续、单步跳过、单步进入、跳出这几个按钮。对学 C 语言的人来说最有价值的两个操作是单步进入和跳出。遇到一个函数调用你想知道里面发生了什么按单步进入就跳进去了看明白了按跳出回到调用处。学到函数这一章的时候用这个方式把递归函数的执行过程走一遍比在纸上画栈帧图清楚得多。这是我个人认为调试器对初学者最大的价值——它不是用来找 bug 的工具而是理解程序执行流程的工具。还有个小技巧调试时把鼠标悬停在一个变量上会弹出一个提示显示它当前的值不用每次都去左侧面板找。看数组和指针的时候特别方便。6.2 指针到底指向哪表达式求值窗口指针是 C 语言最抽象的部分也是调试器能帮上大忙的地方。在调试面板里找到监视区域可以添加表达式。你输入一个指针变量名它会显示这个指针存的地址值输入*p显示它指向的内容输入p-next之类能看到结构体成员。对于初学者理解指针存的是地址、解引用就是访问那个地址上的东西这种看得见的方式比纯讲解有效得多。内存地址的值通常显示成十六进制比如0x7ffd...。看到那个值你就会明白指针不是什么玄学的东西它就是一个数字只不过这个数字被解释成内存地址。学到动态内存分配那一章malloc返回的地址、free之后指针还指着原来的位置但内存已经归还这些概念用调试器跟一遍印象会深很多。有个常见现象要提前说明局部变量的地址每次运行可能都不一样这是操作系统地址随机化的结果不是你的代码有问题。还有释放后的指针如果还去访问表现可能是崩溃也可能是读到垃圾值这两者都不奇怪因为那块内存的状态本来就不确定了。6.3 多文件工程与第一个Makefile学到一定程度你会开始把代码拆成多个 .c 文件和 .h 文件。这时候在 tasks.json 里写单文件的编译命令就不够用了因为${file}只会编译当前打开的那一个。最直接的改法是手动列出所有源文件gcc -g -Wall -stdc11 main.c utils.c stack.c -o app.exe文件少的时候这么做没问题但每加一个文件就要改一次命令迟早会忘。这时候该上 make 了。在项目根目录建一个叫Makefile的文件注意没有扩展名CC gcc CFLAGS -g -Wall -Wextra -stdc11 TARGET app.exe SRCS main.c utils.c stack.c OBJS $(SRCS:.c.o) $(TARGET): $(OBJS) $(CC) $(OBJS) -o $(TARGET) %.o: %.c $(CC) $(CFLAGS) -c $ -o $ clean: del /Q *.o $(TARGET)这段的意思是把每个 .c 编译成 .o最后再链接成可执行文件。好处是改了一个文件只重编那一个大点的项目能省不少时间。Makefile 里的缩进必须是 Tab 而不是空格这是新手写 Makefile 最容易踩的坑报错信息是missing separator看着莫名其妙其实就是缩进字符不对。然后在 tasks.json 里把 command 改成mingw32-make就接上了。这一套配置下来再加文件只需要改 Makefile 里的 SRCS 一行。刚开始学的时候不用急着上 Makefile两三个文件的手动编译完全够用。等到你发现每次改编译命令都要数文件个数的时候就是引入它的时机。工具是为了解决问题才用的提前用只会增加负担。最后分享一个我自己的小习惯每学一个新概念数组、指针、结构体、文件读写单独建一个文件夹里面放一个能跑通的示例程序和一个记录踩坑的文本文件。等你回头复习或者给别人讲的时候这些小项目比任何笔记都好用。配置这种东西调一次就够了难的是坚持把每一章的代码都亲手敲一遍——我见过太多人把 VSCode 配得漂漂亮亮然后就没打开过第二次。
返回列表