
1. 乱码现场还原VC 中文乱码其实是三个独立环节在打架在 VS 里写 C/Cprintf(中文)跑出来是一串问号、方块或者干脆是䏿–‡这种看着像乱码拼音的东西——这个 VS/VC 中文乱码问题我自己从刚入门到现在带新人几乎每年都要重复讲一遍。刚学的时候我跟大多数人一样第一反应是去改控制台字体把 Consolas 换成新宋体结果发现该乱还是乱后来又听人说加个chcp 65001就好了有些人一加就好了有些人加了反而变成一堆方框。之所以会出现这种别人管用我不管用的情况根子在于中文从你键盘敲进去到屏幕上显示出来中间要穿过三道完全独立的编码关口任何一道对不上结果都是乱码但表现可能一模一样。这也是为什么很多人试了五六种办法都没解决——他改的是第二道关口而问题出在第一道。1.1 源文件、字符串常量、控制台各自认一套编码把这条链路拆开看就清楚了。第一道关口是你的.cpp源文件本身它在磁盘上是一堆字节必须有一种编码来解释它可能是 UTF-8也可能是 GBK中文 Windows 上叫代码页 936还可能带 BOM。第二道关口是编译器把字符串字面量翻译成二进制时用的执行字符集也就是中文这四个字最终在.exe里存成哪几个字节——这一步和源文件编码是两回事很多人把它们混为一谈。第三道关口是控制台它是接收端Windows 控制台有自己的输入代码页和输出代码页默认在中文系统上是 936。三道关口里任意两个用了不同的编码乱码就产生了。举个最常见的组合源文件存成 UTF-8没 BOMVS 的 MSVC 编译器在中文系统上默认按 936 去读它此时编译器会把 UTF-8 的三个字节当成三个 GBK 字符去解析这一步就已经错了通常伴随warning C4819警告如果你的源文件存成 GBK编译器也按 GBK 读执行字符集得到 GBK 的D6 D0 CE C4但控制台输出代码页被谁改成了 65001控制台就会拿 UTF-8 的规则去解释 GBK 字节照样乱。所以只调一道关口等于只把三个齿轮里的一颗拧对了另外两颗还在打滑。1.2 用一张表定位你现在卡在哪一环与其盲目地一个个方法试不如先做个判断。下面这张表是我自己总结的快速定位表你对照现象就能大致猜到是哪个环节出了问题现象最可能出问题的环节优先尝试的办法编译时出现C4819警告源文件编码与编译器假设不一致办法一、办法二源码里中文注释显示正常但程序输出乱码执行字符集与控制台代码页不匹配办法二、办法三输出到文件正常输出到控制台乱码控制台接收端代码页问题办法三控制台正常重定向到文件后乱码执行字符集问题办法二、办法五换一台机器就乱自己机器上正常两台机器系统区域设置不同办法一 办法三Debug 输出正常Release 乱码编译选项不一致很常见办法二检查两套配置这张表里有一个现象特别容易被忽略Debug 配置能跑Release 配置乱码。原因通常是有人只在 Debug 的属性页里加了/utf-8Release 那一栏忘了加。我见过一个项目开发三年没人发现直到上线打包才发现发布版的界面文案全是问号。所以后面讲每一种方法时我都建议你在所有配置这个下拉里设而不是只改 Debug。2. 办法一先把源文件编码钉死UTF-8 with BOM 是最省事的起点第一件事不是改代码是搞清楚你手上这个文件到底是什么编码。这一步听起来很基础但我敢说相当一部分人从没认真确认过。VS 打开一个文件时如果文件没有 BOM它会用当前系统区域设置对应的代码页去猜——中文系统上就是 936 去猜。如果文件实际是 UTF-8中文部分的内容就会显示成一堆问号或奇怪符号。有意思的是VS 编辑器在保存时又可能悄悄按它认为的编码保存回去来回几次文件里就混合了两种编码的内容这时候任何单一方案都救不了它。2.1 怎么确认你手上这个 .cpp 到底是什么编码最直接的办法是用 VS 自己的文件 → 高级保存选项这个菜单默认是藏起来的需要先在工具 → 自定义 → 命令里把它拖出来。打开后能看到当前文件和行的编码我一般会直接把它改成UTF-8 带签名也就是 UTF-8 with BOM然后保存。另一个办法是用十六进制工具看一眼文件开头三个字节如果是EF BB BF就是带 BOM 的 UTF-8如果是FF FE就是 UTF-16LE如果直接就是正常文本内容那多半是 GBK 或无 BOM 的 UTF-8。也可以用 VS Code 打开右下角会显示当前识别到的编码点一下可以通过编码重新打开这是判断文件真实编码最快的方式之一。这里有个实操细节值得单独说如果你的文件里出现了乱码显示千万不要直接按 CtrlS。因为 VS 保存的是当前内存中你看到的字符如果它把原来的字节按错误编码解释成了乱码字符你再保存等于把错误解释固化进文件原来的字节就永久丢了。正确做法是先撤销、或者直接关掉不保存然后用高级保存选项或 VS Code 的通过编码重新打开去用正确的编码重新解读这个文件。2.2 为什么我推荐带 BOM 而不是纯 UTF-8现在的通行规范是UTF-8 不带 BOM这个在 Linux、Web、Python 世界里基本是铁律。但在 Windows MSVC 这个组合里BOM 反而是一个非常有用的信号MSVC 看到 BOM 就无条件按 UTF-8 解析源文件不用管系统区域设置是什么。这意味着你的代码在中文系统、英文系统、日文系统上编译结果完全一致不会出现我这里好好的同事那边编译出来就乱码这种事。代价也有两个需要你知道并接受。第一某些老旧的编译器或构建脚本可能会被 BOM 卡住比如某些版本的 GCC 处理带 BOM 的文件会报奇怪的错如果你的代码要跨平台编译BOM 就得慎重。第二一些版本控制工具的 diff 会因为 BOM 变动产生噪音。所以我的建议是纯 Windows/MSVC 项目源文件一律 UTF-8 with BOM省心跨平台项目源文件 UTF-8 无 BOM改用编译选项去强制指定编码也就是下面要讲的第二种办法。2.3 批量转换的实操与注意事项项目里如果有几百个文件一个个手动转太慢。我一般用一段 PowerShell 脚本批处理逻辑是把文件按 GBK 读进来再按 UTF-8 with BOM 写出去# 把目录下所有 .cpp / .h 从 GBK 转成 UTF-8 with BOM $utf8Bom New-Object System.Text.UTF8Encoding($true) $gbk [System.Text.Encoding]::GetEncoding(936) Get-ChildItem -Recurse -Include *.cpp,*.h,*.hpp | ForEach-Object { $text [System.IO.File]::ReadAllText($_.FullName, $gbk) [System.IO.File]::WriteAllText($_.FullName, $text, $utf8Bom) Write-Host converted: $($_.FullName) }跑之前一定要先提交一次代码或者先拷一份备份出来。原因很简单这个脚本是假定所有文件都是 GBK如果目录里本来就混着几个已经是 UTF-8 的文件它会把它们二次破坏。稳妥的做法是分批处理先拿几个文件试试用 VS Code 确认结果没问题再批量跑。我自己就吃过这个亏一次性转了整个目录结果里面几十个已经改好的 UTF-8 文件全变成了锟斤拷只能从版本库里回滚重来。另外还有一个常见的坑.rc资源文件、.asm汇编文件、.def模块定义文件它们的编码规则和.cpp不完全一样不要一股脑用同一个脚本转。资源文件后面第 7 节会单独讲。3. 办法二/utf-8 编译开关把源字符集和执行字符集一次锁死源文件编码统一之后第二个要处理的是编译器这一层。这一层是 MSVC 特有的坑很多人搞了十年 C 都没完全理清楚原因在于 MSVC 对源文件编码的处理逻辑确实比 GCC/Clang 复杂——后者基本默认 UTF-8前者会根据有没有 BOM、有没有编译选项、系统区域设置是什么来综合判断。3.1 MSVC 解释源文件的完整决策链我把它整理成一条判断链你对照着看自己文件现在会被怎么处理文件开头有 UTF-8 BOMEF BB BF→ 无条件按 UTF-8 解析源文件。文件开头有 UTF-16 BOMFF FE或FE FF→ 按 UTF-16 解析。命令行里明确给了/source-charset:xxx→ 按指定的代码页解析。命令行里给了/utf-8它隐含指定了源字符集为 UTF-8→ 按 UTF-8 解析。以上都没有 → 按系统当前的 ANSI 代码页解析中文系统就是 936。同时如果文件里含有 936 无法表示的字节会抛C4819警告。看完这条链你就能理解一个现象为什么同一份代码别人编译有个C4819警告你没有。因为他那份文件没有 BOM 而系统是中文 936他文件里有 UTF-8 的中文字节编译器读的时候发现了这字节在 936 里不该出现而你的文件带了 BOM走的是第 1 条不产生警告。3.2 /utf-8、/source-charset、/execution-charset 到底谁管谁这三个选项经常被搞混我用一句话区分/source-charset:utf-8只管怎么读源文件也就是第一道关口。/execution-charset:utf-8只管字符串字面量在 .exe 里存成什么字节也就是第二道关口。/utf-8是前两个的合并写法等价于同时指定/source-charset:utf-8 /execution-charset:utf-8。这里必须强调一个特别容易误解的点加了/utf-8之后你的程序在.exe里存的中文字节就是 UTF-8而不是 GBK。如果你的控制台还停留在默认的 936 代码页那么默认行为是UTF-8 字节 936 解释 乱码。所以很多人加了/utf-8之后发现乱码从一串问号变成了一堆方块或者奇怪的符号会以为选项没用甚至更糟实际上它起作用了只是控制台那一环还没对上。解决办法就是/utf-8配合办法三一起用这是目前 Windows 上最主流、最省心的组合。反过来也有一种情形源文件是 GBK你不加任何选项MSVC 按 936 读执行字符集也是 936字符串在 exe 里是 GBK 字节控制台也是 936四道关口全对上程序显示正常。这就是什么都不加也能跑的原因——它靠的是全链路 GBK 的一致性。但一旦你想把源文件改成 UTF-8或者想把项目迁到跨平台环境这套一致就崩了。3.3 在 VS 属性页和 CMake 里怎么配VS 属性页的操作路径是右键项目 → 属性 → 配置属性 → C/C → 命令行 → 在其他选项里填/utf-8。注意上方的配置下拉要选所有配置平台选所有平台这是防止 Debug/Release 不一致的关键一步。VS2019 之后更友好的做法是在 C/C → 所有选项 → 源字符集和执行字符集两个下拉里直接选效果等价而且直观好找。用 CMake 的项目这么写if(MSVC) add_compile_options(/utf-8) # 顺手把常见的几个中文相关警告开起来便于早发现问题 add_compile_options(/W4 /wd4819) endif()/wd4819是关掉C4819警告因为用了/utf-8之后这个警告已经没意义了留着会淹没有效警告。如果你的项目通过target_compile_options给单个目标加要记得PUBLIC和PRIVATE的区别头文件里如果有中文字符串接口使用者也需要同样的选项这种情况要用PUBLIC。3.4 一个常见的误解加了 /utf-8 控制台就一定不乱码吗不是。/utf-8只解决了前两道关口第三道关口控制台它管不着。而且更麻烦的是它只影响字符串字面量的编码如果你从文件里读进来的字符串、从网络收到的字节、从数据库取出来的字段它们的编码完全由数据源决定和/utf-8一点关系都没有。我见过有人加了/utf-8之后字面量正常了但从文件里读的中文还是乱然后跑来找我说/utf-8是骗人的。其实他需要的是在读取环节做一次编码转换这部分第 7 节会讲。还有一个附带效果值得提一下/utf-8也会影响__FILE__宏、#include路径解析里的中文。如果你的文件路径里有中文目录名加上之后某些老版本工具链可能反而出问题这种情况我建议整个项目路径都改成纯英文能省掉一大堆莫名其妙的麻烦。4. 办法三改控制台的接收端SetConsoleOutputCP 与 chcp 65001前面两道关口解决完字符串在 exe 里是 UTF-8 字节了现在要解决的是控制台怎么正确显示它。这个环节的机制很有意思理解了它你就能解释为什么重定向到文件正常、输出到屏幕乱码这种反直觉现象。4.1 printf 到控制台和 printf 到文件走的是两条完全不同的路关键点在于 C 运行时的底层写操作会根据目标句柄的类型走不同分支。当stdout指向一个真正的控制台时CRT 会走WriteConsoleA这条路而WriteConsoleA的行为是把你给的窄字节串按当前控制台输出代码页翻译成 Unicode再交给控制台渲染。所以只要把控制台输出代码页设成 65001UTF-8你给它的 UTF-8 字节就会被正确翻译成 Unicode屏幕上就正常了。而当stdout被重定向到文件或管道时WriteConsoleA这条路走不通目标不是控制台CRT 直接WriteFile把原始字节写出去不做任何转换。这就是为什么有人会遇到这种组合现象屏幕上乱码program.exe out.txt之后用 VS Code 打开 out.txt 却是正常的 UTF-8 文本反过来也可能屏幕正常、重定向后文件乱码。一旦你理解了这一点很多玄学现象就都能解释了。4.2 SetConsoleOutputCP(65001) 应该放在哪一行最直接的做法是在main开头加上#include windows.h #include stdio.h int main(void) { SetConsoleOutputCP(65001); // 输出代码页设为 UTF-8 SetConsoleCP(65001); // 需要读中文输入时也一起设 printf(中文测试\n); return 0; }这两行分别管输出和输入。只调SetConsoleOutputCP的话屏幕上显示正常了但你用scanf或cin读中文输入时还会乱所以一般两个一起设。要注意SetConsoleCP在某些老版本 Windows 上会让输入法行为变得奇怪如果你的程序不做中文输入就别设它。如果你不想改代码临时验证可以用chcp命令。在 CMD 里敲chcp 65001回车然后运行你的程序效果一样。要恢复的话敲chcp 936。我调试的时候经常这么干比改代码快。在 VS 里也可以把启动前命令设置成chcp 65001路径是项目属性 → 调试 → 命令参数旁边的环境或者直接做一个.bat启动脚本。4.3 代码页 65001 的几个老毛病字体、输入、老系统65001 这个代码页在 Windows 上属于历史包袱比较重的东西有几个坑必须提前知道。字体不匹配会显示方块。控制台默认字体是 Consolas它不含中文字形。在 936 代码页下控制台会自动回退到中文字体但在 65001 下这个回退逻辑在旧版本上有问题结果就是一堆方块。解决办法是在控制台标题栏右键 → 属性 → 字体选新宋体或微软雅黑这类含中文的字体。Windows Terminal 和 VS 较新版本的终端对这块处理得好很多基本不用手动调。老程序可能会崩。有些依赖 936 代码页做MultiByteToWideChar转换的老库在 65001 下会转出错误结果。如果你引用了第三方 DLL出现设了 65001 之后别的功能坏了大概率是这个原因。VS 的输出窗口和真正的控制台是两回事。在输出窗口里看到的中文乱码SetConsoleOutputCP完全无效因为那不是一个控制台。这种乱码通常是调试器的编码显示问题和你的程序编码无关别浪费时间在这上面。646 陷阱表现处理方式字体不含中文字形显示为方块控制台属性里换新宋体第三方库依赖 936其他功能异常不用 65001改用办法五VS 输出窗口乱码仅调试窗口乱忽略或改用外部控制台调试重定向后文件无变化文件乱码这是执行字符集问题回到办法二5. 办法四#pragma execution_character_set(utf-8) 与老项目的过渡方案如果你的项目因为某些原因不能用/utf-8——比如还在用 VS2013 或更早的工具链或者有几千个文件不敢一次性改——那#pragma execution_character_set(utf-8)是一条很实用的过渡路线。它的历史比/utf-8早从 VS2010 SP1 开始就有了。5.1 这条 pragma 只干一件事看清楚它的名字execution_character_set。它只影响执行字符集也就是字符串字面量最终在 .exe 里存成什么字节。它完全不管源文件怎么被读取。所以用它的时候有个前提源文件的编码必须是编译器能正确理解的那一种否则中文在解析阶段就已经错了这条 pragma 再厉害也救不回来。它的典型用法是这样#include stdio.h #include windows.h #pragma execution_character_set(utf-8) int main(void) { SetConsoleOutputCP(65001); printf(这是 UTF-8 执行字符集\n); return 0; }注意它和/utf-8的一个区别/utf-8会改变#include中文路径的解析方式而这条 pragma 不会它只改字符串字面量。这在某些场景下反而是优点比如你的项目路径里有中文目录用/utf-8之后某些工具会出问题用这条 pragma 就没事。5.2 它和 /utf-8 的关系以及为什么要小心重复设置如果你在项目里同时用了/utf-8和这条 pragma编译器会报一个重复设置类的警告大致意思是你重复指定了执行字符集虽然通常不会导致错误结果但会在编译日志里制造噪音。所以原则是能上/utf-8就不要用 pragmapragma 只作为局部或过渡方案。另一个要注意的是这条 pragma 的作用范围——它是从它在文件里出现的位置开始生效一直到文件结束所以它必须写在文件靠前的位置。如果某个.cpp里只有一部分字符串需要 UTF-8 执行字符集把它写在对应位置之前也是可以的但我不建议这么用会让后来维护的人看着很懵。5.3 大工程里只改一个文件的写法接手老项目的时候我一般不会一次性把整个项目切到 UTF-8因为风险太大涉及所有源文件、所有字符串、所有依赖库的接口契约。我通常的做法是新建一个charset.h头文件内容就是那条 pragma 加注释说明然后在需要新增中文功能的.cpp里#include它。这样新代码用 UTF-8老代码维持原状两边各自自洽。等新功能稳定了、有时间做整体迁移了再统一切/utf-8。这个增量迁移的思路在实操中非常有用。因为它把一次大爆炸式的改造拆成了一个个可控的小步骤每次改动都能单独测试、单独回滚。我见过太多团队想一步到位结果改了两天编译错误上千条最后只能全部回滚白干。6. 办法五绕开多字节走宽字符 setlocale _setmode 这条正统路线前面四种方法本质上都是在窄字符 代码页这个框架里打补丁。第五种方法换了个思路干脆不用窄字符用宽字符。这也是 Windows 原生 API 一直推荐的做法所有 Windows API 都有 A 和 W 两个版本W 版本才是完全体。它的好处是不依赖控制台代码页天然规避了字节 代码页的歧义问题。6.1 为什么宽字符能一分钱不花地解决歧义wchar_t在 Windows 上是 16 位一个中文汉字用一个wchar_t表示不存在几个字节拼起来才是一个字这种模糊地带。当你用wprintf输出时CRT 会走WriteConsoleW直接把宽字符交给控制台控制台拿到的就是明确的 Unicode 码位不需要猜代码页自然就不会乱码。这是它最核心的价值。要走这条路需要两步设置 locale以及把stdout切到宽字符模式。#include stdio.h #include locale.h #include io.h #include fcntl.h int main(void) { setlocale(LC_ALL, ); // 让 CRT 支持宽字符输出 _setmode(_fileno(stdout), _O_U16TEXT); // stdout 切到 UTF-16 模式 wprintf(L宽字符中文测试\n); return 0; }setlocale(LC_ALL, )里的空字符串意思是用系统默认区域设置这是最省心的写法。如果想显式指定可以用setlocale(LC_ALL, zh-CN)但不建议写死会影响国际化。6.2 _O_U8TEXT、_O_U16TEXT、_O_WTEXT 三个模式的区别这三个名字很像实际行为差别不小用错了会碰到很诡异的输出。我用一张表说清楚模式输出编码到控制台的行为重定向到文件BOM_O_U16TEXTUTF-16LE走 WriteConsoleW不受代码页影响写 UTF-16LE不写_O_WTEXTUTF-16LE同上写 UTF-16LE写 BOM_O_U8TEXTUTF-8转 UTF-8 字节后仍需控制台代码页为 65001写 UTF-8不写从这张表能直接得出一个实用结论如果你希望程序在任何代码页的控制台下都能正确显示中文选_O_U16TEXT不要选_O_U8TEXT。因为_O_U16TEXT走的是WriteConsoleW直接给控制台 Unicode 码位和代码页彻底解耦。而_O_U8TEXT只是把宽字符转成 UTF-8 字节最后还是绕回字节 代码页的老问题。如果重定向到文件时希望别的程序能识别编码选_O_WTEXT更保险因为它会写 BOM记事本、VS Code、Excel 都能自动识别。我在导出报表这类场景里就用_O_WTEXT用户体验最好。6.3 踩坑切了模式之后 printf 为什么直接崩这是个经典坑我第一次遇到的时候也懵了很久。原因很直白一旦把stdout切到宽字符模式就不能再用printf了。因为printf输出的是窄字符和当前流模式冲突在 Debug 版下会触发_invalid_parameter断言弹出一个对话框在 Release 版下可能什么都不输出静悄悄地失败这种静默失败特别难查。所以切换模式之后要保证这个流上所有输出都用wprintf、fwprintf、std::wcout。如果项目里已经用了大量printf改动量会很大这时候我一般选择在入口处统一包装一个输出函数把所有输出都收敛到一处将来要换编码只改这一个函数。这是个很实用的架构习惯值得单独说一下编码相关的处理一定要收口不要散落在几百个 printf 调用里。6.4 重定向到文件时的真实字节调试这类问题时有一招特别好用把输出重定向到文件然后用十六进制工具看实际字节。比如_O_U16TEXT下输出中文件里应该是2D 4E小端 UTF-16LEGBK 下是D6 D0UTF-8 下是E4 B8 AD。看一眼字节你就知道程序到底输出了什么编码不用再猜。我排查这类问题的时候基本都会先做这一步比反复改代码试快得多。7. 那些不在控制台里的中文乱码文件读写、MFC 资源、数据库、VS Code前面五种方法解决的是程序往控制台输出中文这个最主要的场景。但实际项目里乱码还常出现在另外几个地方它们的机制和前五节不太一样混在一起讲容易乱我单独拆出来说。7.1 fstream 写文件的中文到底存成了什么用std::ofstream写中文时写进文件的字节完全取决于你传给它的字符串里是什么编码ofstream本身不做任何转换。所以如果你的字符串来自/utf-8编译出来的字面量文件里就是 UTF-8如果来自 936 代码页下读进来的内容文件里就是 GBK。问题通常出在读写两端不一致程序写的是 UTF-8另一个程序按 GBK 读就乱了。我处理文件 IO 的原则一直是项目内部统一 UTF-8只在边界处转换。具体做法是写文件时如果对方程序要求 GBK就在写入前做一次显式的WideCharToMultiByte(936, ...)转换读文件时如果对方给的是 GBK就在读入后立刻用MultiByteToWideChar(936, ...)转成宽字符内部全程用宽字符处理输出时再决定用什么编码。这套做法虽然麻烦一点但边界清晰出问题的时候很容易定位是哪一端错了。这里要提醒一个非常容易踩的坑不要用MultiByteToWideChar时不带MB_ERR_INVALID_CHARS标志。它会让无效字节被静默替换成一个默认字符导致数据悄悄损坏你还查不出来。加上这个标志后遇到无效字节会直接返回失败你就能知道输入是脏的。7.2 MFC/Win32 资源文件与 .rc 编码.rc资源文件的编码规则和.cpp是两套体系不能套用上面的办法。VS 的资源编辑器在保存.rc时会按照它自己的规则处理通常是把.rc存成当前系统代码页同时在文件头写一条#pragma code_page(...)来声明。如果你手动把.rc改成 UTF-8编辑器可能就读不出来了。在 MFC 或 Win32 项目里项目属性 → 高级 → 字符集这个设置会决定是走_UNICODE还是_MBCS。用_UNICODE的项目字符串字面量前要加L或_T()宏否则会出现界面文案变问号的情况。这个坑在把一个老 MBCS 项目切到 Unicode 时特别集中代码里几百处中文全都要改成_T(中文)漏一处就少一行字。我的建议是切换之前先用正则搜一遍所有中文字符串引用列个清单改完再 grep 一遍确认没有漏网的。7.3 往数据库写中文变问号的链路这个场景在热词里也经常被提到很多人第一反应是数据库编码不对实际上链路更长。以 MySQL 为例一次写入要经过C 程序里的字符串编码 → 客户端连接字符集 → 服务端表字段字符集 → 存储引擎。任何一环不匹配都可能出问号或乱码。排查的顺序我一般是倒着来先看表字段和库的字符集是不是utf8mb4再看连接建立时客户端字符集设置对不对MySQL C API 里用mysql_options设MYSQL_SET_CHARSET_NAME或在连接后调mysql_set_character_set最后才看程序里的字符串编码。最常见的错误是连接字符集默认是latin1而你送的字符串是 UTF-8。这种情况下即使表字段是utf8mb4数据也已经在客户端到服务端这一段被错误转换了存进去就是乱码。一定要用utf8mb4而不是utf8因为 MySQL 的utf8只有三个字节存不下一些少见的字符。7.4 VS Code 的乱码是另一套配置顺带说下经常被混淆的 VS Code。它的编辑器和 VS 的编辑器在编码处理上是独立的。VS Code 默认用 UTF-8 打开文件遇到 GBK 文件会乱码解决办法有两种临时用右下角的编码菜单通过编码重新打开选 GB2312或者一劳永逸地在设置里打开files.autoGuessEncoding让它自动猜编码。集成终端里的中文乱码则是另一回事那是终端本身的代码页问题一般通过给终端 profile 加上chcp 65001来解决。这两个问题的根因完全不同别用同一个思路去修。8. 我自己用的排查顺序从后往前两分钟定位讲了五种办法实际遇到问题时该按什么顺序试我自己的习惯是从链路末端往前查因为后面的环节改动成本最低、验证最快。8.1 五步定位法第一步先把输出重定向到文件用十六进制工具看实际字节。这一步能立刻告诉你程序输出的编码到底是什么是判断问题在前端还是后端的关键分水岭。如果文件里的字节是对的比如是合法的 UTF-8那问题百分百在控制台那一环如果文件里就是错的问题在源文件编码或执行字符集。第二步看编译日志有没有C4819警告。有的话基本可以确定源文件编码和编译器的假设不一致直接去处理源文件编码。第三步确认 Debug 和 Release 两套配置的编译选项是否一致。我见过太多次Debug 好好的 Release 乱码就是这个原因。第四步检查控制台代码页。用chcp看一眼当前值再决定要不要在程序里调SetConsoleOutputCP。第五步如果上面四步都排除了还乱那就换宽字符方案从根上绕开这一整套问题。8.2 不建议动的那个开关系统区域设置 Beta UTF-8最后说一个很多人会推荐、但我个人不推荐的做法Windows 控制面板 → 区域 → 管理 → 更改系统区域设置 → 勾选使用 Unicode UTF-8 提供全球语言支持。勾上之后系统的 ANSI 代码页会变成 65001理论上能让一堆 UTF-8 程序不用改代码就正常。但它的代价是全局性的所有依赖 936 的老程序都会受影响有些会乱码有些甚至直接起不来。我试过在一台开发机上开这个开关结果几个内部老工具全挂了只能赶紧关掉。而且这个开关一旦打开会影响到系统里所有程序的默认行为是典型的为了一件小事牺牲全局稳定性。如果只是自己程序的中文显示问题用前五种方法里的任意一种在程序内部解决就好不要动系统级的开关。除非你在做全新开发、且完全确定所有依赖都支持 UTF-8否则这个选项我建议一直保持关闭。顺带提一句如果你确实需要这台机器上的某个程序以 UTF-8 为默认代码页运行又不想改全局设置可以在程序的 manifest 里声明activeCodePage为 UTF-8。这是 Windows 10 1803 之后提供的机制作用范围只限这一个程序比改系统设置安全得多。这个做法在正式发布的产品里比较常见值得了解一下。