-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
109 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# C++翻译阶段中的编码转换-执行字符集与源字符集 | ||
|
||
## 前言 | ||
|
||
编码问题是开发过程中常见且令人困扰的挑战,尤其对于使用 C/C++ 技术栈的开发者来说,更是常见的难题。 | ||
|
||
本节的重点在于解释: | ||
|
||
- 源字符集(即源代码文件的编码方式,但并非代码文件本身的编码)。 | ||
|
||
- 执行字符集(编译器用来编译代码的字符编码)。 | ||
|
||
- 终端字符集(终端使用何种编码)。 | ||
|
||
- 编译器在 C++ 翻译阶段如何处理源代码中的字符编码转换。 | ||
|
||
- 操作系统区域设置影响编译器默认的”*执行字符集*“、”*源字符集*“ | ||
|
||
希望简单的看完本节之后,当遇到乱码问题,不要再过度惊慌,而是慢慢的查找问题所在。 | ||
|
||
> [!IMPORTANT] | ||
> 我们测试的环境是 `Ubuntu22.04`、`Windows11`。且我的 Windows11 开启了全局 `utf-8` 。测试结果确保统一的。 | ||
> | ||
> 使用 GCC 编译器展示,不过 clang 与 msvc 也都会进行测试。 | ||
## 例子 | ||
|
||
我们先用一段简单的代码来展示常见的问题。 | ||
|
||
```cpp | ||
#include <iostream> | ||
|
||
int main(){ | ||
std::cout<< "乐呵\n"; | ||
} | ||
``` | ||
|
||
这是一段简单的代码,你可以使用 vscode 等编辑器将其转换为 `GB18030` 的中文编码存储。 | ||
|
||
> [!TIP] | ||
> | ||
> GB2312、GBK 和 GB18030 可以简单理解为从旧到新的中文字符集标准。在 **中国大陆区域**,如果没有修改系统的区域设置以及开启全局 `utf-8`,Windows 默认编码通常是这三者中的某一个。 | ||
假设我们将这段代码以 GB18030 的编码存储为一个 `test.cpp` 文件,那么问题来了,你需要如何编译它且确保不出现乱码呢? | ||
|
||
```shell | ||
g++ .\test.cpp -o test | ||
``` | ||
|
||
如果只是这样普遍的编译不添加任何选项的编译,运行无法得到正常的输出,只有一些**乱码**: | ||
|
||
```shell | ||
�ֺ� | ||
���� | ||
``` | ||
|
||
我告诉各位,当你看到这种类似的符号,就是**解码失败**,由于字符集不匹配或在解析过程中出现了问题,导致无法正确显示原本的字符。 | ||
|
||
根据 cppreference 文档中翻译阶段的:[阶段 5:确定字符串字面量的公共编码](.E9.98.B6.E6.AE.B5_5.EF.BC.9A.E7.A1.AE.E5.AE.9A.E5.AD.97.E7.AC.A6.E4.B8.B2.E5.AD.97.E9.9D.A2.E9.87.8F.E7.9A.84.E5.85.AC.E5.85.B1.E7.BC.96.E7.A0.81); | ||
|
||
我们可以知道:**字符字面量、字符串字面量中的所有字符从*源字符集*转换为*执行字符集***。 | ||
|
||
也就是说编译器在编译代码的时候会先进行一个编码转换,那么问题来了,我们当前环境的“*源字符集*”是什么?“*执行字符集*”又是什么呢? | ||
|
||
你可能认为“*源字符集*” 就是我们的文件编码 `GB18030` 嘛,但是其实并非如此,你只要没有显式设置,那么就是遵循系统的默认区域设置,区域是什么?当然是 **utf-8** 了。 | ||
|
||
> [!TIP] | ||
> | ||
> Linux 可以使用 `locale` 命令查看,Windows 使用 [`chcp`](https://learn.microsoft.com/zh-cn/windows-server/administration/windows-commands/chcp)。 | ||
> | ||
> 由于我的 Windows 系统开启了全局 `utf-8`,chcp 得到的会是 `65001`。 | ||
那么*执行字符集*呢?还是那句话,只要没有显式设置,就会遵循系统的默认区域设置,也是 `utf-8`。 | ||
|
||
- 这意味着,在翻译阶段(阶段五)中,**编译器并不会对我们代码中的 GB18030 字符串字面量进行任何转换**。最终,编译出的可执行程序会以 `UTF-8` 形式强行解码 GB18030 编码的字符串,这就导致了输出时出现乱码。 | ||
|
||
那么如何解决这个问题呢?很简单,我们指明它的**源字符集**为 GB18030 不就是了? | ||
|
||
```shell | ||
g++ .\test.cpp -o test -finput-charset=gb18030 | ||
``` | ||
|
||
执行字符集无需设置,按照区域默认,就是 `utf-8`。那么在翻译阶段(阶段五)中,编译器会将我们代码中的 GB18030 字符串字面量转换为 utf-8。最终编译出的可执行程序就能正常输出。 | ||
|
||
其实大家还需要考虑终端的编码,即使程序完全正确,终端的编码不对依然可能有乱码,这就是环境问题,而非程序本身了。另外,如果想要显式指明执行字符集,可以使用选项:`-fexec-charset`,也就是: | ||
|
||
```shell | ||
g++ .\test.cpp -o test -finput-charset=gb18030 -fexec-charset=utf-8 | ||
``` | ||
|
||
## 补充 | ||
|
||
以上提到的选项 gcc 与 clang 都可以使用。 | ||
|
||
Visual Studio 2015 Update 2 及之后版本分别用 [`/source-charset`](https://learn.microsoft.com/zh-cn/cpp/build/reference/source-charset-set-source-character-set?view=msvc-170) 和 [`/execution-charset`](https://learn.microsoft.com/zh-cn/cpp/build/reference/execution-charset-set-execution-character-set?view=msvc-170) 指定源字符集和执行字符集。并且还增加了一个新的选项:[**`/utf-8`**](https://learn.microsoft.com/zh-cn/cpp/build/reference/utf-8-set-source-and-executable-character-sets-to-utf-8?view=msvc-170)(将源字符集和执行字符集设置为 UTF-8)。更多详情也可以参阅微软文档。 | ||
|
||
此外,如果你编辑器中你看到的代码没有乱码,但实际编译后的程序会出现了乱码。这也可能是因为编辑器可能会根据系统区域设置使用 GB18030 等别的编码查看文件,而不是 UTF-8。 | ||
|
||
在此还需要特别强调两点: | ||
|
||
1. **不要以为你的代码文件是 UTF-8 编码,编译器就自动认为源字符集是 UTF-8**。 | ||
2. **不要以为代码文件是 UTF-8 编码,就没有非 UTF-8 的字符**。某些 IDE 的编码处理可能会有问题,导致你无法直接察觉文件中的字符集问题。建议使用 **VSCode** 等支持多种字符集的编辑器来进行查看,确保编码一致。 | ||
|
||
## 总结 | ||
|
||
我们其实没涉及太多复杂的例子,主要围绕一个小点展开,但这背后的原理并不复杂。事实上,我见过很多开发者频繁地解决乱码问题,但他们可能并不清楚问题的根源和解决的具体过程。了解这些细节后,我们可以更有信心地应对类似的乱码问题,而不至于手忙脚乱。 | ||
|
||
掌握 **源字符集** 和 **执行字符集** 的概念,了解操作系统默认区域设置对编译器的影响,明白 C++ 在翻译阶段中对字符串的编码转换,以及如何通过正确的设置避免编码不匹配带来的问题,将帮助我们在编码时避免许多潜在的麻烦。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,3 +21,4 @@ | |
- [C++ 包管理,编译构建配置等问题](C++%20包管理,编译构建配置等问题.md) | ||
- [单例跨dll保证唯一定义吗?](单例跨dll保证唯一定义吗?.md) | ||
- [文件写入与数据丢失:从用户态到内核缓冲](文件写入与数据丢失:从用户态到内核缓冲.md) | ||
- [C++翻译阶段中的编码转换-执行字符集与源字符集](C++翻译阶段中的编码转换-执行字符集与源字符集.md) | ||
Check failure on line 24 in src/卢瑟日经/README.md
|