资讯动态

VS2019中文乱码彻底解决:源文件编码与控制台代码页全解析

发布时间:2026/10/16 4:49:46 来源:尧图企业网站定制
写C/C的老哥一定遇到过这个场景从别人手里接过来一个VS2019工程打开代码文件注释和字符串全是“锟斤拷”或者自己写的控制台程序代码里明明是“中文测试”编译运行后输出一堆“涓冩祴璇”。这个问题的根源基本都指向一件事——VS2019默认用GBK编码保存源文件。这篇文章不搞虚的把这个问题的完整链路拆开乱码到底卡在哪一环、源文件编码怎么统一、编译器要做什么配置、控制台输出要怎么处理一次讲透。1. 乱码分四种先对号入座再动手网上搜“VS2019 中文乱码”能搜出一堆互相矛盾的解法原因就是“乱码”这个词被滥用得太厉害了。同样是乱码可能是编辑器打开的问题可能是程序运行输出的问题还可能是编译器压根没法解析的问题。不先对号入座盲目改配置只会越改越乱。1.1 在VS2019里打开源文件就是乱码这种最常见的场景是你从别人那里拷来一个.cpp文件或者在Git上克隆了一个仓库用VS2019打开后中文注释全部变成乱码。这里有个容易误判的点文件可能本身没问题是VS2019的编码检测机制在捣乱。VS2019编辑器对不带BOM签名的UTF-8文件经常识别不出来在中文Windows环境下会默认按GBKANSI代码页936去解读。UTF-8编码的三个字节在GBK里会被拼成两个看起来很像中文的“汉字”典型的产物就是“涓冩祴璇”这种火星文。怎么确认用记事本打开同一个文件如果正常说明文件本身没问题纯粹是VS编辑器打开的编码选错了。或者用VS2019的“文件 高级保存选项”看一眼当前编码如果显示“ANSI”或“GBK”而你确信这份代码应该是UTF-8那基本就是这个情况。1.2 代码编辑正常编译运行后控制台乱码这个场景更让人抓狂VS2019里代码显示完全正常编译也不报错但一运行printf或者cout输出的中文在控制台里全是乱码。这一类的成因和1.1完全不是一回事。代码能正常显示说明源文件编码和编辑器读取编码是一致的运行时乱码说明编译器写进可执行文件里的字符串编码和控制台用来解码的代码页对不上。比如源文件是UTF-8编译器也按UTF-8把字符串存进了程序文件但Windows控制台默认代码页还是936GBK拿GBK去解码UTF-8字节自然就是乱码。需要特别提醒的是如果你在VS2019里用默认方式新建文件、直接用printf(“中文”)在自带的控制台里通常是正常的因为一切都走GBK链路。一旦你换了终端、重定向输出到文件、或者编译器开启了UTF-8相关的选项这条链路就被打断了。1.3 编译阶段直接报错或警告还有一种更直接的压根跑不到运行阶段编译就挂了最常见的错误是error C2001常量中有换行符error C2143语法错误缺少“;”warning C4819该文件包含不能在当前代码页中表示的字符这个场景的根源通常也是VS2019默认GBK编码。你从某个地方拿来的文件是UTF-8但没带BOMVS编译器默认按GBK去切分源码里的字符多字节序列被切成了半截中文字符串直接被认为“没有正常闭合”编译器自然一脸懵。如果只是警告C4819很多人会直接忽略但这种警告往往是更大问题的前兆后面讲编译配置的时候会细说。1.4 重定向到文件后打开乱码最后一种情况更容易藏在项目交付阶段程序在控制台显示一切正常但你把输出重定向到文件比如app.exe result.txt然后用Notepad、VSCode打开发现中文全是乱码。为什么控制台正常、文件里乱码因为控制台能正常显示是控制台代码页和程序输出编码恰好一致文件可不管你控制台是什么它只是忠实地把字节存下来。等编辑器用另一种编码去打开这个文件乱码就出现了。这个场景的核心矛盾依然是程序输出字节的编码和应用场景需要的编码不一致。先搞清楚自己属于哪一类再看下面几节才不会走弯路。2. 为什么VS2019默认用GBK以及编码问题的本质2.1 “默认GBK”的实际含义在中文Windows环境下VS2019新建C源文件时默认保存编码继承的是操作系统的ANSI代码页简体中文系统就是GBK代码页936。很多人以为“默认GBK”是VS自己拍脑袋决定的其实不是。Windows系统层面就没把UTF-8设为默认编码方案VS作为一个运行在Windows上的IDE它的很多默认行为必须兼容系统的代码页设定。直到Windows里开启了“使用Unicode UTF-8提供全球语言支持”的Beta选项整个系统会切换到UTF-8代码页VS的新文件默认编码才会跟着变成UTF-8——注意这里说的是“beta选项”对老软件兼容性影响很大一般不推荐为了个IDE去开启。这里有一个重要的延伸知识点GBK是为了兼容中文老系统设计的双字节编码不是全球通用的标准。同一份文件拷到英文系统、日文系统、或者交给强制使用UTF-8的Linux工具链极大概率就是乱码。问题初期不明显是因为你身边所有人都用着同一种中文Windows环境。2.2 三个字符集源字符集、执行字符集、控制台代码页网上很多教程只告诉你“点哪里改哪里改完就好”不告诉你为什么。真正理解这个问题只需要掌握三个概念第一个源字符集source charset。编译器读取源码文件时用什么编码去解码这些字节。如果文件是UTF-8的编译器却按GBK读那源码里的字符串字面量在编译器眼中已经是错的了后面全完蛋。第二个执行字符集execution charset。编译器把源码里的字符串字面量转换成什么编码写进最终生成的可执行文件里。运行的时候程序拿到的其实就是这一串字节。第三个终端代码页console code page。程序输出这一串字节给控制台控制台按什么编码去解析它并映射到字体显示。源字符集、执行字符集、终端代码页三个环节只要有一个对不上输出就会乱码。你可以把编码想象成三把尺子源文件用A尺子量编译器按B尺子理解运行后又用C尺子显示但只要有一把尺子刻度不一样最终读出来的长度就永远是错的。2.3 乱码特征值对照表判断乱码属于哪种错位看特征比看运气有效。下面是几个典型的乱码样式和它们背后的原因现象本质原因典型场景锟斤拷、锘GBK被当成UTF-8解码无效字节被替换后再次转码用UTF-8方式打开了GBK文件涓冩祴璇、鐮佸UTF-8被当成GBK解码用GBK方式打开了UTF-8文件或大量空格解码时遇到目标字符集无法映射的字符编码表之间缺字符映射C2001/C2143编译错编译器按GBK切分无BOM UTF-8源码字符序列断裂源码带中文且未加编译选项“锟斤拷”这个经典乱码本质上是UTF-8解码失败时生成的替换字符UFFFD编码为EF BF BD又被当作GBK去显示最后生成了“锟斤拷”这三个字。看到锟斤拷基本可以断定文件实际是GBK/ANSI但读取方用了UTF-8。“涓冩祴璇”则是反过来UTF-8的中文字节序列被按两个字节一组切分在GBK里凑出了三个看起来中文的字符它其实是UTF-8文件被GBK解码的典型标志。2.4 为什么BOM签名那么重要前面多次提到“带BOM的UTF-8”这里单独说明一下。BOMByte Order Mark字节序标记就是UTF-8文件开头的三个字节EF BB BF相当于一个声明我是UTF-8编码。在Windows体系里BOM不是可有可无的装饰品。MSVC编译器对不带BOM的UTF-8文件默认不自动识别仍然按系统ANSI代码页去读。这就是为什么有的人把文件另存为“UTF-8无签名”之后编译反而报了C2001——因为编译器压根不认这个“无签名UTF-8”它还在按GBK去解析你的文件。所以对于C的VS2019项目最省心的一条路是文件保存成UTF-8带BOM。VS编译器看到BOM就自动切换到UTF-8不会再出现源码被误解的情况。后面讲到/utf-8编译选项时你才会真正理解“带BOM没问题不带BOM也不是不行但必须配套设置”。3. 把源码统一成UTF-8手动、批量到默认配置3.1 单个文件“另存为UTF-8带签名”如果手头只有一两个文件需要处理最直接的办法是VS2019的另存为。操作路径文件 另存为 保存按钮旁边的下拉箭头 编码保存。在弹出的编码选择框里找到“Unicode (UTF-8 带签名) - 代码页 65001”点击确定。这个选项在不同语言版本里叫法略有区别但认准“UTF-8带签名”或者“UTF-8 with signature”就行。注意不要选“Unicode (UTF-8 无签名)”除非你在编译配置里准备好了/utf-8选项。对VS2019的C项目来说无签名UTF-8反而是最容易踩坑的选项。保存完之后可以用“高级保存选项”确认一下当前文件状态显示“UTF-8带签名”才算成功。3.2 批量转码脚本项目文件多的时候一个个另存为会改到怀疑人生。这时候用PowerShell批量转码更靠谱。下面这个脚本把指定目录下所有C/C源文件从GBK批量转为UTF-8带BOM$src D:\code\legacy_project $gbk [System.Text.Encoding]::GetEncoding(936) $utf8Bom New-Object System.Text.UTF8Encoding($true) Get-ChildItem -Path $src -Recurse -Include *.c,*.cpp,*.h,*.hpp | ForEach-Object { $text [System.IO.File]::ReadAllText($_.FullName, $gbk) [System.IO.File]::WriteAllText($_.FullName, $text, $utf8Bom) Write-Host Converted: $($_.FullName) }简单解释一下GetEncoding(936)就是GBKUTF8Encoding($true)表示生成带BOM的UTF-8。脚本用GBK去读文件内容再以UTF-8带BOM写回。这里我必须泼一盆冷水这个脚本默认所有目标文件都是GBK编码。如果你的目录里混着已经是UTF-8的文件脚本会把这些UTF-8文件按GBK来读读出来的内容再以UTF-8保存直接二次破坏。所以批量转码前务必先做两件事第一用Git或备份工具把整个目录提交/复制一份留好退路第二转完后抽查几个文件的diff结果确认中文注释和字符串没有发生变化只是编码换了。脚本里的Write-Host只是提示输出正式使用可以去掉。3.3 用EditorConfig让新文件默认UTF-8能不能让VS2019从源头就按UTF-8新建文件而不是每个文件都要手动转一次可以用项目根目录的.editorconfig文件。在解决方案根目录新建一个.editorconfig内容可以这样写root true [*.{c,h,cc,cpp,hpp}] charset utf-8 end_of_line crlf indent_style space indent_size 4VS2019原生支持EditorConfig不需要额外装插件。charset utf-8这一行对VS2019的影响主要有两个方向一是新建C/C文件时默认按UTF-8编码保存二是在VS里打开文件时尝试以UTF-8解释。如果你希望带BOM有些项目会写charset utf-8-bom但实测在不同VS小版本上表现不一致我个人的建议是在VS2019的C项目里希望带BOM就直接用“另存为带签名”处理存量文件新文件用EditorConfig统一成无签名UTF-8同时配合编译选项/utf-8使用。这样整套方案在跨平台和Git场景下更干净。3.4 注册表方法可作备选除了EditorConfig社区里还流传一种注册表改法。在注册表编辑器中定位到HKEY_CURRENT_USER下对应VS2019内部版本16.0的VisualStudio配置节点找到General项新建一个名为DefaultCharset的字符串值赋值为2重启VS后再新建C源文件默认编码会变成UTF-8带签名。我个人的测试结果是这个方法在部分VS2019小版本上有效在有其他插件干预时又失效过稳定性一般。它属于“能用但是非官方”的野路子升级VS、清理环境配置时容易丢失。我的态度是它适合一个人单机开发时图省事不适合作为团队项目的标准方案——团队标准方案还是EditorConfig加编译选项更可控。4. C/C编译配置/utf-8要加对地方别和项目字符集混为一谈4.1 /utf-8在哪个位置加源码编码统一到位只是第一步编译器未必认。前面说过MSVC对不带BOM的UTF-8文件默认按ANSI代码页解析所以要么文件带BOM要么在编译选项里明确告诉编译器“我的源代码是UTF-8”。VS2019里的操作路径项目右键 属性 配置属性 C/C 命令行 其他选项输入/utf-8应用。这里有个必须注意的细节Debug和Release、x86和x64是分开的配置。你在Debug下加了/utf-8切到Release编译又报C2001不是/utf-8没用而是这个配置里没有加。最省事的做法是“配置管理器打开所有配置都改一遍”或者直接编辑.vcxproj文件把AdditionalOptions/utf-8/AdditionalOptions加到所有需要的配置节点下。/utf-8实际上是两个选项的合成/source-charset:utf-8和/execution-charset:utf-8。前者告诉编译器源码按UTF-8读后者告诉编译器字符串字面量的执行字符集也按UTF-8写进可执行文件。一个管入一个管出缺一不可。4.2 加了/utf-8之后源码文件必须是真正的UTF-8这又是一个反直觉的坑如果你的源码文件实际是GBK编码却加了/utf-8编译器会老老实实地按UTF-8去读GBK文件结果比不加选项时更糟。UTF-8的解码器遇到不合法的GBK字节序列可能直接报错或者产生莫名其妙的字符。所以正确的顺序永远是先把文件本身转成UTF-8编码再考虑编译选项。文件编码和编译选项是两套东西先保证文件自身编码正确再利用编译选项消除歧义。4.3 项目属性里的“字符集”不是干这个用的很多教程会把“项目属性 常规 字符集”从“使用多字节字符集”改成“使用Unicode字符集”然后告诉你这样中文就不会乱码。这是典型的误传。这个“字符集”选项只影响TCHAR、_T宏的展开方向影响的是你调用的Windows API是A版还是W版比如MessageBoxA还是MessageBoxW。它不会改变编译器对源码文件的解码方式也不会改变执行字符集。换言之你改了这个编辑器里中文该怎么乱还是怎么乱运行输出该乱码还是乱码。真正和文件编码相关的只有源文件本身保存的编码、/utf-8编译选项、执行字符集、控制台代码页。4.4 加了/utf-8还是乱码的三种可能如果文件已经是UTF-8无签名编译选项也加了/utf-8源码里的中文在编辑器里看着也没问题但输出还是乱那问题大概率不在编译阶段而在运行时输出阶段。第一种可能控制台代码页还是936。程序在/utf-8下生成的字符串是UTF-8字节但终端用GBK去解必然乱码。这种情况和编译器没关系解决方式是切终端代码页下一节讲。第二种可能你用的是wprintf、wcout这类宽字符输出却同时开了_O_U16TEXT相关的模式没配对或者宽字符串里包含被转码搞乱的字符。宽字符链路和执行字符集的关系比较绕建议单独测试。第三种可能源码文件虽然是UTF-8但某些你引用的第三方库的头文件还是GBK编译器在包含这些头文件时报C4819或乱码。这种情况属于混合编码工程建议把第三方库自己的头文件也统一转码或者对特定头文件单独指定源字符集。另外提一个容易在旧博客里看到的写法#pragma execution_character_set(utf-8)。这个指令在VS2015之后基本上已经不起作用了属于历史遗留方案。看到这种教程可以直接跳过别浪费时间。5. 控制台代码页输出的最后一公里5.1 控制台默认代码页与输出编码不一致很多人在这一关会浪费大量时间。源文件已经是UTF-8带BOM编译选项也加了/utf-8代码在VS2019编辑器里显示完美结果一运行控制台全是乱码。问题出在编译器生成的字符串是UTF-8字节但Windows控制台默认代码页是936GBK。你可以自己在命令行输入chcp看看绝大多数中文Windows环境返回的就是936。在936代码页下显示UTF-8字节等于要求一个说粤语的人去听普通话内容是对的但解码规则对不上。控制台显示正常需要同时满足程序输出编码和控制台代码页编码一致。代码页936配GBK输出正常代码页65001配UTF-8输出正常。一旦双方不一致就是乱码。这就是为什么本地控制台看着正常换一台电脑、换一种终端环境同样的程序就乱码了。5.2 临时切换控制台代码页最简单的验证方式在程序运行时打开cmd窗口先执行chcp 65001再运行程序如果中文正常了说明就是控制台代码页的问题。想在你自己的工具类程序里自动处理可以在程序启动时调用#include windows.h int main() { SetConsoleOutputCP(CP_UTF8); printf(中文输出测试\n); return 0; }SetConsoleOutputCP(CP_UTF8)是Windows API相比在代码里调用system(chcp 65001)它更干净不额外启动子进程。注意这里的CP_UTF8常量就是65001代码页。这种方式适合单人使用的调试工具、测试小程序但不建议硬编码进大型项目里。因为切换控制台代码页会影响当前控制台环境里其他程序的输出行为还会让一些依赖GBK代码页的老代码“突然乱码”。更稳妥的处理是把控制台相关逻辑从业务代码里拆出来或者直接用下面这种宽字符方案。5.3 宽字符输出绕开代码页的终极方案最稳的输出方案不是去凑代码页而是直接让程序输出宽字符UTF-16由控制台自己负责把宽字符渲染出来。这样无论控制台代码页是936还是65001输出都能正常显示。#include cstdio #include fcntl.h #include io.h int main() { _setmode(_fileno(stdout), _O_U16TEXT); wprintf(L中文输出测试\n); return 0; }_setmode(_fileno(stdout), _O_U16TEXT)的作用是把标准输出切换成UTF-16模式之后wprintf输出的宽字符串直接走控制台的Unicode路径不再经过代码页转换环节。代码页936的cmd也照样能显示中文。这个方案的代价是需要改代码字符串字面量要加L前缀printf系列要换成wprintf系列。对于历史项目来说改造量很大所以我会把这个方案定位成“新项目优先、老项目按需引入”。如果你只是调试输出偶尔要打印中文不需要为此把整个项目推倒重来。5.4 日志文件与调试输出的特殊考虑控制台之外还有两个容易忽略的输出场景。第一个是日志文件。程序把日志写到文件时如果按窄字符输出写进文件的就是当前执行字符集的字节。执行字符集是UTF-8则文件是UTF-8执行字符集是GBK则文件是GBK。问题是很多人写完日志后用Notepad打开Notepad默认按UTF-8解析GBK文件就是乱码。最省事的做法是日志文件永远明确指定编码输出UTF-8并且把这一条写进项目规范让打开日志的同事都知道该用什么编码打开。第二个是调试输出。用OutputDebugStringA往调试器或DebugView里输出中文时调试工具也有自己的编码假设。这个场景不用太纠结控制台代码页但建议保持和项目执行字符集一致避免调试器那边看到的也是乱码。6. 我踩过的坑和现在的固定做法6.1 几个反复折腾人的坑第一个坑只改项目属性的“字符集”。一度以为把“使用多字节字符集”改成“使用Unicode字符集”就能解决中文输出乱码结果编译和运行都毫无变化。后来才明白这两个选项压根不管源码编码前面4.3已经解释过这里再次强调别在这上面浪费时间。第二个坑把文件另存为UTF-8无签名却没加/utf-8。自己觉得“我已经存成UTF-8了”结果编译冒出一堆C2001错误。当时完全不理解为什么UTF-8反而报错后来才知道是MSVC不认无签名UTF-8。从此在VS2019里要么文件带BOM要么明确加/utf-8不会再有“只改一半”的操作。第三个坑批量转码把自己的文件洗白成乱码。我曾经写脚本处理一批老代码当时没检查目录里已经混进了几个UTF-8文件脚本全部按GBK去读再按UTF-8写直接把几个正常文件变成了“锟斤拷”。从那以后我做任何编码批量操作之前都会先看一遍Git状态确保可以一键还原。第四个坑加了/utf-8后反而控制台乱码。当时以为是编译选项没生效排查半天最后发现是控制台代码页还停在936。这就引出一个非常重要的排查思路分清阶段。代码在VS里显示正常说明文件编码和编辑器的读取没问题编译不报错说明编译器读源码也没问题那就把焦点放在运行时输出阶段去查控制台代码页而不是回头怀疑文件编码。第五个坑注释里的中文也会出事。很多人以为只要代码逻辑里没有中文注释乱就乱了不影响编译。但C4819警告往往就是注释里的生僻字或全角字符触发的一旦项目开启了“把警告当错误”的选项整个编译直接失败。注释也是文件内容的一部分同样需要编码统一。6.2 现在我的组合方案经过这么多来回我现在给团队定的标准比较固定分三种情况第一种纯Windows平台的新项目。源码文件统一UTF-8带BOM编译配置全配置加/utf-8控制台输出一律用宽字符。这套组合基本不会在编码上出幺蛾子也是我遇到问题时的默认兜底方案。第二种跨平台项目。源码文件统一UTF-8无签名MSVC编译配置加/utf-8GCC/Clang保持默认UTF-8交接文档里明确标注“仓库所有文本文件均为UTF-8”。跨平台项目尽量不带BOM避免部分Linux工具链对BOM处理得不够优雅。第三种存量老项目。一次性转码的代价如果能承受就按前面PowerShell脚本统一转成UTF-8带BOM跑一遍构建和用例。如果文件太多、风险太大那就分模块转每转一个模块都验证一次至少保证新增文件全部从UTF-8带BOM起步。最后分享一个小技巧处理这类编码问题不要靠眼睛猜直接在VS2019里使用“高级保存选项”看当前文件编码同时用chcp看一眼命令行窗口的代码页再对照本文第2.3节的乱码特征表基本五分钟内就能定位到具体环节。把编码当成和缩进一样的工程规范来对待一次配置到位后面能省下大把时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑