MSYS2 UCRT64 + g++ C++ 中文输出乱码 完整全过程定稿文档
一、环境说明
- 操作系统:简体中文 Windows
- 编译环境:MSYS2 UCRT64,编译器
g++(MinGW-w64 UCRT 版本) - 代码编辑器:Vim,
.cpp源码文件保存为 UTF-8(无 BOM) - 终端:
- Mintty(UCRT64 自带终端模拟器,已在选项中将字符集设置为 UTF-8)
- Windows 原生 CMD(微软自带控制台程序)
二、最初遇到的问题
编写包含中文的 C++ 代码,直接使用命令 g++ a.cpp 编译,编译成功,生成a.exe。 在 UCRT64 的 mintty 终端运行 ./a.exe,输出中文全部显示为乱码方块。
一开始猜想:mintty 已经设置字符集为 UTF-8,理应正常显示中文,但实际运行依旧乱码。
三、原理拆解
概念 1:两个独立编码,必须配对才能正常显示中文
乱码核心规则:程序输出字节的编码 和 终端解析字节所使用的编码 必须一致。
修改终端的编码设置,不会自动改变 C++ 程序输出的字节编码。
概念 2:GCC 读取源码的核心规则
g++不会自动探测 cpp 源码文件的编码。- 编译时不指定参数,GCC 会读取Windows 系统 ANSI 代码页,作为读取源码的默认编码。
- 简体中文 Windows,系统 ANSI 代码页默认是 936(GBK)。
👉 我的场景: 我的.cpp文件是 Vim 保存的 UTF-8 编码,但 GCC 默认使用 GBK 规则读取 UTF-8 字节。 在编译读取源码阶段,中文就已经解析出错,编译进 exe 内部的字符串本身就是损坏的字节。 就算源码是 GBK、GCC 读取源码成功,exe 内部存储 GBK 字节;而 mintty 使用 UTF-8 解析字节,仍然会乱码。
⚠️ 区分三个极易混淆的概念:系统 ANSI 代码页、CMD 快捷方式属性、chcp 命令
- 系统 ANSI 代码页:Windows 底层全局参数(控制面板→区域→管理→更改系统区域设置)。所有新打开 CMD 窗口启动时的默认代码页。默认 936(GBK);开启 Beta UTF-8 选项可改为 65001,但属于全局改动,大量老旧软件会中文乱码,风险高,不推荐。
- CMD 右键【属性】 :仅当前 CMD 快捷方式的预设配置,只对使用这个快捷方式打开的 CMD 窗口生效,不修改系统底层 ANSI 代码页。
chcp 65001:仅当前打开的这一个 CMD 会话临时生效 。关闭窗口,设置直接丢失;新开 CMD,自动读取系统 ANSI 代码页,变回 936GBK。 重要:在 CMD 执行 chcp,完全不影响 Mintty(UCRT64 终端),二者是两套独立终端。
Mintty 补充:Mintty 是独立终端模拟器,不使用 Windows 控制台 chcp 这套机制,它的字符集在 mintty 选项内单独配置。
概念 3:UCRT64 (mintty) 和 CMD 的互相切换
在 UCRT64 的 mintty 窗口内,可以直接输入cmd回车,在同一个窗口内启动嵌套的 CMD 子进程,原地进入 CMD 环境。
- UCRT64 bash → CMD:mintty 提示符输入
cmd回车 - 嵌套 CMD 切回 UCRT64 bash:在 CMD 提示符输入
exit回车,关闭 CMD 子进程,回到 UCRT64 bash 环境。
注意:嵌套 CMD 里面执行的
chcp 65001只对这个嵌套 CMD 会话生效;执行exit退出 CMD 之后,chcp 设置直接消失,不会影响外层 mintty。
四、排查与试错全过程
-
初始现象:mintty 已经设置 UTF-8,直接编译运行程序,中文乱码。
-
第一次测试:打开 Windows CMD,执行
chcp 65001,临时把当前 CMD 窗口切换为 UTF-8,运行a.exe,中文正常显示。 -
发现 chcp 的局限性:关闭这个 CMD 窗口,重新新开 CMD,执行
chcp查看,代码页变回 936GBK。原因:chcp 只是会话临时设置,不会永久保存,新窗口启动读取系统 ANSI 代码页。 -
深入分析:
chcp只能改变终端 "如何解析收到的字节",无法改变 exe 内部字符串编码,也无法解决 GCC 编译读取源码阶段的编码错误。 -
尝试方案 A(代码内处理):在 C++ 代码添加
SetConsoleOutputCP(CP_UTF8);,引入<windows.h>头文件。- 原理:程序运行时主动告诉 Windows UCRT 运行库,stdout 输出 UTF-8 字节。
- 缺点:代码需要额外增加语句,依赖 Windows 头文件,跨平台不方便。
-
尝试方案 B(编译参数,最终选定方案) 源码保持 Vim UTF-8 保存,编译命令增加参数:
g++ a.cpp -o a.exe -finput-charset=UTF-8 -fexec-charset=UTF-8
-finput-charset=UTF-8:告诉 GCC,cpp 源码文件是 UTF-8 编码,请使用 UTF-8 读取源码。-fexec-charset=UTF-8:告诉 GCC,将源码中的中文字符串,以 UTF-8 字节存入 exe 程序。
- 遇到新疑问:之前
chcp 65001后运行程序中文正常,后面同样操作却乱码。
原因:
chcp 65001只能控制终端显示,不能修复编译阶段已经损坏的字符串。上次成功,是当时编译出来的 exe 内部字符串是合法 UTF-8 字节;如果编译时不带 UTF 参数,exe 里面中文已经是错误字节,就算 chcp 切换到 65001,依旧乱码。exe 编译完成之后,内部字节固定,chcp 无法修改 exe 内容。
五、最终效果
源码以 UTF-8 保存,编译带上 UTF 相关参数。 程序运行时cout直接输出 UTF-8 字节流;Mintty 终端字符集本身是 UTF-8,编码匹配。 ✅ 不需要在 C++ 代码写SetConsoleOutputCP ✅ 不需要每次打开终端执行chcp 65001 ✅ 不修改 Windows 系统全局 ANSI 代码页,不会影响其他老旧软件。
六、全部方案对比汇总
表格
| 方案 | 操作 | 优点 | 缺点 |
|---|---|---|---|
编译参数 -finput-charset=UTF-8 -fexec-charset=UTF-8 |
编译命令附加参数 | 只影响本次编译,不改动系统,mintty 下直接正常显示中文 | 每次编译需要加参数(可配置 bash 别名自动携带) |
代码 SetConsoleOutputCP(CP_UTF8) |
C++ 源码添加代码 | 编译出来的 exe,双击在 CMD 打开也能正常显示中文 | 需要修改代码,依赖 windows.h 头文件 |
chcp 65001 |
CMD 内执行命令 | 临时窗口生效,快速测试 | 窗口关闭失效;不能解决编译阶段编码问题;CMD 和 mintty 互相不影响 |
| 系统 Beta UTF8 全局选项 | 控制面板开启 Beta UTF8 | 所有新 CMD 默认 UTF8,GCC 默认按 UTF8 读源码 | 全局改动系统,大量老旧软件中文乱码,风险很高,不推荐 |
七、核心结论(速记)
chcp只是当前窗口临时改控制台编码,关窗口失效,CMD 和 Mintty 互相独立。- GCC 默认读取源码的编码取自 Windows系统 ANSI 代码页(简体 Windows 默认 936GBK),GCC 不会自动探测 cpp 文件编码。
- Mintty 的 UTF-8 设置只管终端渲染,不能改变程序输出字节编码。
- 最优方案:源码保存为 UTF-8,编译带上
-finput-charset=UTF-8 -fexec-charset=UTF-8,配合 mintty 的 UTF8 字符集,无需额外代码,中文正常输出。 - 临时测试方案:在 CMD 中执行
chcp 65001,仅当前 CMD 窗口临时切换为 UTF8,可快速验证程序中文输出;但窗口关闭就失效,无法解决源码编译阶段的编码问题。 chcp 65001不能修复编译阶段产生的错误字符串:如果编译时没有加 UTF 参数,exe 内中文字节已经损坏,就算执行 chcp 65001,运行程序依旧乱码;上次 chcp 能正常,是因为当时编译出来的 exe 内部字符串本身就是合法 UTF-8 字节。- 在 mintty 中输入
cmd进入嵌套 CMD;输入exit即可退出 CMD,回到 UCRT64 的 bash 环境。