附录 我自己的初步答案出现在问题底部。
我正在将一个过时的VC6 C++/MFC项目转换为VS2013和Unicode,基于utf8everywhere.org的建议。
在这个过程中,我一直在研究Unicode、UTF-16、UCS-2、UTF-8、标准库以及对Unicode和UTF-8的STL支持(或者更确切地说是标准库缺乏支持)、ICU、Boost.Locale,当然还有需要UTF-16
wchar
的Windows SDK和MFC API。随着我对以上问题的研究,一个问题一直困扰着我,我无法得到一个满意的澄清答案。
考虑C库函数
mbstowcs
。该函数具有以下签名:size_t mbstowcs (wchar_t* dest, const char* src, size_t max);
第二个参数
src
是(根据 文档)一个包含待解释多字节字符的 C 字符串。多字节序列应该以初始换码状态开始。我对这个多字节字符串有疑问。我了解到,编码方式可能因字符串而异,并且标准没有规定编码方式。MSVC 文档 也没有说明特定的编码方式。
我的理解是,在 Windows 上,预计该多字节字符串应该使用当前区域设置的 ANSI 代码页进行编码。但在这一点上,我的清晰度开始消失。
我一直在思考源代码文件本身的编码是否会影响 mbstowcs 在 Windows 上的行为。此外,我也困惑于上述代码片段在编译时和运行时发生了什么。
假设您将字符串字面量传递给 mbstowcs,如下所示:
wchar_t dest[1024];
mbstowcs (dest, "Hello, world!", 1024);
假设这段代码在Windows机器上编译。假设源代码文件本身的代码页与编译器运行的机器上当前语言环境的代码页不同。编译器是否会考虑源代码文件的编码方式?生成的二进制文件是否会受到源代码文件的代码页与编译器运行时所使用的语言环境代码页不同的影响?
另一方面,也许我理解有误 - 也许运行时机器的活动语言环境决定了对字符串文字的期望代码页。因此,保存源代码文件的代码页是否需要与最终运行程序的计算机的代码页匹配?这似乎很奇怪,让我难以相信会是这种情况。但正如您所看到的,我的表述在这里缺乏清晰度。
另一方面,如果我们将对mbstowcs的调用更改为显式传递UTF-8字符串:
wchar_t dest[1024];
mbstowcs (dest, u8"Hello, world!", 1024);
我假设
mbstowcs
总是能够完成正确的操作 - 不受源文件代码页、编译器当前区域设置或代码运行计算机的当前区域设置的影响。我对此正确吗?特别是针对我上面提出的具体问题,我希望能得到明确的解答。如果我所提的任何问题不恰当,我也希望得知。
附录 从@TheUndeadFish的答案下面的冗长评论以及这里一个非常相似的问题的答案,我相信我有一个初步的答案来回答自己的问题,我想提出来。
让我们跟随源代码文件的原始字节,看看整个编译到运行时行为的过程中实际字节是如何被转换的:
C++标准“表面上”要求源代码文件中的所有字符都是ASCII的一个名为“基本源字符集”的96个字符子集。但是请参阅以下要点。实际上,关于这96个字符在源代码文件中的字节级编码,标准没有指定任何特定的编码,但所有96个字符都是ASCII字符,因此实际上不存在有关源文件采用哪种编码的问题,因为现有的所有编码都使用相同的原始字节表示这96个ASCII字符。
然而,字符文字和代码注释可能通常包含基本96个字符之外的字符。通常编译器支持这一点(尽管C++标准不要求这样做)。源代码的字符集称为“源字符集”。但编译器需要在其内部字符集(称为“执行字符集”)中具有这些相同的字符,否则在编译器实际处理源代码之前,这些缺失的字符将被某个其他(虚拟)字符(例如方块或问号)替换-请参见下面的讨论。当字符出现在“基本源字符集”之外时,编译器如何确定用于对源代码文件中的字符进行编码的编码是由实现定义的。
请注意,编译器可以为其内部“执行字符集”使用与源代码文件所表示的字符集不同的字符集(无论如何编码)。这意味着即使编译器知道源代码文件的编码(这意味着编译器还了解源代码字符集中的所有字符),编译器也可能被迫将源代码字符集中的某些字符转换为“执行字符集”中的不同字符(从而丢失信息)。标准规定,这是可以接受的,但编译器不得将“源字符集”中的任何字符转换为“执行字符集”中的空字符。
C++标准没有说明“执行字符集”的编码,也没有说明需要在“执行字符集”中支持哪些字符(除了“基本执行字符集”中的字符外,其中包括“基本源字符集”中的所有字符以及少量其他字符,例如“NULL”字符和退格字符)。似乎很难找到任何清楚地记录这个过程的文档,即使是由Microsoft提供的。也就是说,编译器如何确定源代码文件的编码和相应的字符集,或者选择哪种编码在编译源代码文件时用于“执行字符集”,并没有明确指出。
在MSVC的情况下,编译器似乎会尽最大努力尝试选择给定源代码文件的编码(和相应的字符集),如果失败,则回退到运行编译器的机器的当前区域设置的默认代码页。或者,您可以采取特殊步骤使用提供每个源代码文件开头的正确字节顺序标记(BOM)将源代码文件保存为Unicode。这包括UTF-8,其中BOM通常是可选的或排除在外,在MSVC编译器读取的源代码文件的情况下,您必须包括UTF-8 BOM。
至于“执行字符集”及其在MSVC中的编码,请继续下一个要点。
然后,编译器开始读取源文件,并将源代码文件字符的原始字节从“源字符集”的编码转换为“执行字符集”中相应字符的(可能不同的)编码(如果给定字符存在于两个字符集中,则将是相同的字符)。
忽略代码注释和字符文字,所有此类字符通常都位于上述“基本执行字符集”中。这是ASCII字符集的子集,因此编码问题无关紧
感谢那些抽出时间阅读这里冗长的答案的人。
mbstowcs
已在 mbstowcs 中有文档记录。src
字符串是使用调用线程的区域设置进行解释的。为了获得可靠的结果,您可以设置调用线程的本地设置,或者使用 Microsoft 的扩展_mbstowcs_l
,并带有一个区域设置参数。 - IInspectable