Skip to content

Commit

Permalink
新增 C++ 翻译阶段中的编码转换-执行字符集与源字符集 章节
Browse files Browse the repository at this point in the history
  • Loading branch information
Mq-b committed Jan 26, 2025
1 parent 9b32dd4 commit 6439577
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 0 deletions.
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++ 在翻译阶段中对字符串的编码转换,以及如何通过正确的设置避免编码不匹配带来的问题,将帮助我们在编码时避免许多潜在的麻烦。
1 change: 1 addition & 0 deletions src/卢瑟日经/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@
- [C++ 包管理,编译构建配置等问题](C++%20包管理,编译构建配置等问题.md)
- [单例跨dll保证唯一定义吗?](单例跨dll保证唯一定义吗?.md)
- [文件写入与数据丢失:从用户态到内核缓冲](文件写入与数据丢失:从用户态到内核缓冲.md)
- [C++翻译阶段中的编码转换-执行字符集与源字符集](C++翻译阶段中的编码转换-执行字符集与源字符集.md)

Check failure on line 24 in src/卢瑟日经/README.md

View workflow job for this annotation

GitHub Actions / check

Files should end with a single newline character

src/卢瑟日经/README.md:24:57 MD047/single-trailing-newline Files should end with a single newline character https://github.com/DavidAnson/markdownlint/blob/v0.33.0/doc/md047.md

0 comments on commit 6439577

Please sign in to comment.