对于普通(便携式)软件,字符编码是一个痛苦的世界。问题(和潜在的解决方案)是:
A) 文本文件可以采用任何随机/“文本编辑器定义”编码。
要解决这个问题,有 4 个选项:
-
期望以特定编码(例如 UTF-8)输入并拒绝支持其他任何内容(如果文件中的数据对于您选择的编码无效,则会生成错误消息)。这会惹恼一些用户(例如,国家标准与 CNS 11643 等不兼容的地方)。
-
支持多种编码,并让用户选择期望的编码(例如,基于命令行参数)。这对用户来说有点不方便,对你来说很痛苦。
-
支持多种编码,并尝试自动检测文件使用的编码。这对用户来说更方便一点,直到它猜错并成为一个主要的烦恼(并且你不能将猜错编码的机会减少到零)。
-
支持多种编码,让用户根据需要选择编码,如果用户没有指定,则自动检测。这对用户来说是最好的选择(对软件开发者来说也是最糟糕的选择)。
对于这些选项,我会使用第一个(我会说“输入文件必须是 UTF-8”,部分原因是 UTF-8 已经变得非常普遍且得到很好的支持,部分原因是其他所有编码都被证明对技术而言更糟糕原因)。请注意(根据您的结果)您的输入文件极有可能是 UTF-8 格式。
B) 无论编译器对char 使用什么,都是实现定义的(可以是 ASCII,可以是 EBDIC,也可以是其他任何东西),并且可以是有符号的或无符号的。
在这种情况下,假设 ASCII 是“非常安全的”(对于可移植性)。假设 UTF-8 是第二个最佳选择,但它会在“可能已签名”char 值上执行任何数学运算(例如右移等)的任何代码产生问题。
C) stdin、stdout、stderr 管道也是随机/实现定义的。
这与上一个问题类似,除了最佳解决方案(“假设 ASCII”)要困难得多(尤其是当您想要输出包含输入文件中的文本片段的错误消息等时)。为此,我很想尽可能多地使用 ASCII,但如果必须的话,我会作弊并输出 UTF-8。如果操作系统(或外壳)无法处理 UTF-8,它会造成混乱,但大多数用户会理解(并且可以通过将输出传递到文件来解决它)。最好的替代方案(用于用户输出)是使用 GUI 而不是使用stdout,但这会产生大量额外问题(并导致第二大额外问题 - 诸如错误消息等的国际化)。
D) 编译器对wchar 的假设是随机/实现定义的(可能是 UTF-16,可能是 UTF-32,也可能是其他任何东西;它甚至可能是一个 8 位编码而不是“宽”)。
这里唯一明智的选择是认识到wchar 是一个不可用的故障,绝不应该(在任何情况下)用于任何事情。
更具体地说,wchar 是基于以往历史错误的历史错误。本质上,在早期,微软和 Sun 决定采用 UCS-2(“所有 Unicode 代码点都适合 16 位”的假设),但很快就被打破了。为了解决这个问题,Microsoft 和 Sun 转而使用 UTF-16,但 Microsoft 主要在 little-endian 机器上运行并选择了 UTF-16LE,而 Sun (Java) 的目标是 big-endian 机器并选择了 UTF-16BE。 wchar 扩展于 1995 年被添加到 C 中,同时公司(Microsoft、Sun)做错了所有事情并且没有做任何相互兼容的事情;所以wchar 最终变成了一个“我们不知道标准是什么,所以我们的标准根本就不是标准”的笑话。对于 C(和 C++),这个问题在 2011 年得到修复,在 <uchar.h> 中引入了 char16_t (UTF-16) 和 char32_t (UTF-32),但采用速度很慢(例如,微软仍然懒得打扰与 C99)。
请注意,问题的另一部分是人们想假设一个 wchar 是一个完整的可打印字符,而这几乎从来不是这种情况(例如,即使对于 UTF-32,其中一个 wchar 是一个完整的字符Unicode代码点有组合代码点);这会破坏任何“宽字符”实现的任何好处(即使您的代码根本不可移植并且您知道 wchar 实际上是什么)。
最好的解决方案(特别是如果您选择“期望输入文件使用 UTF-8”来解决第一个问题)是使用存储在 uint8_t 中的 UTF-8(这样任何人都不会混淆 @987654339 @ 是)。
在这种情况下; “将文件中的输入转换为您的内部字符编码”可以变成“无所事事地将 UTF-8 转换为 UTF-8”;并且“将您的内部字符编码转换为stdout 想要的任何内容”变成“几乎什么都不做(从uint8_t 转换为char)将UTF-8 转换为ASCII(或UTF-8)”。换句话说,它可以非常接近“对所有东西使用相同的编码”。