为神马不要在代码里写中文(二)

在很多个明天之后,我想起来要更新blog了。上次话说到一半,这次回来把结果补上,说的是字符串”a好人”在string literal中的故事。这个字符串有1个洋文字母和2个中文,测试的环境是win7中文版,非unicode的代码页设置的是中文简体,应该是gbk的编码。为了让结果更加整齐一些,我先把出现的结果列一下。win7 x64是little endian的,我直接打印的字符串各个字节,所以,对应的字符编码就自己调顺序吧:

// (1) "a好人"用UTF-8编码为:

61 | e5 a5 bd | e4 ba ba | 00

//(2) "a好人"用GBK编码为:

61 | ba c3 | c8 cb | 00

//(3) "a好人"用UTF-16 LE编码为:

61 00 | 7d 59 | ba 4e | 00 00 

我用’|’分割了一下结果,实际打印结果里是木有那个’|’的。不过还没完,因为编译器不一定能识别出正确的编码,所以会出现一些诡异的结果。编码(1)所列的utf-8字符如果强制被当做gbk来理解,对应的字符串是“a濂戒汉”,也就是1个英文3个汉字。以此字符串为基准我也列一些结果,待会儿填到表格中。

//(4) "a濂戒汉"用GBK编码和(1)结果相同:

61 | e5 a5 bd | e4 ba ba | 00

//(5) "a濂戒汉"用UTF-16 LE编码结果为:

61 00 | c2 6f | 12 62 | 49 6c  | 00 00

再次说明,“a濂戒汉”是utf-8的“a好人”被错误的以GBK编码方式解释而得到的错误字串,本身并没有写在源码中。在我测试的时候,utf-8的源代码文件中字符串就是以(1)那种编码储存的,gbk的源代码文件中字符串就是以(2)那种编码储存的。

下面贴一下结果,输入的源代码文件以gbk,utf-8,和utf-8 with BOM(byte order mark)三种格式储存,分别在不同的编译器下运行上一篇中提到的代码

源文件编码 \ 编译器 gcc4 vc6 vc2003.net vc2005 vc2008 vc2010
UTF-8(char) (1) (1) (1) (1) (1) (1)
GBK(char) (2)* (2) (2) (2) (2) (2)
UTF-8 BOM(char) N/A N/A (1) (2) (2) (2)
UTF-8(wchar_t) (3) (5) (5) (5) (5) (5)
GBK(wchar_t) (3)* (3) (3) (3) (3) (3)
UTF-8 BOM(wchar_t) N/A N/A (3) (3) (3) (3)

*gcc4使用GBK编码的源文件时,加了编译参数-finput-charset=gbk。

这个表格给出的结果很有趣。首先说说char string literal,几乎所有的编译器都得到了和源文件编码一致的结果……但这里面隐含着一个巧合,那就是对utf-8的处理。对于gcc来说,utf-8是默认的编码格式,但对于微软的编译器来说,当前codepage,在我这里也就是gbk,才是默认的编码格式。所以,UTF-8(char)的结果在所有编译器中都一致不过是巧合而已,只要看看下一行UTF-8 BOM(char)的结果就明白了。

vc的编译器据说是自动识别文件编码的,但对于90%都是英文字符的源代码,utf-8和gbk几乎无法自动区分开来,所以加了BOM在文件头才可以让vc识别出真正的编码。不过加了BOM的源代码gcc是不认识的,而vc自己也直到vc2003才开始支持。加上BOM之后,vc才可以真正理解utf-8格式的源代码,这时vc2003和gcc依旧保持一致,仍把char string literal直接编译成utf-8,但是!!但是!!坑爹的事情从vc2005开始出现,此版本之后vc会强制对char string literal进行转码,它会强制转换成当前的codepage,也就是gbk!

可以这么理解,对于char string literal,自vc2005开始,无论源文件是什么格式,它统统会转码到当前的codepage。而gcc,vc6和vc2003则是和源码文件中的编码保持一致——虽然实际上vc2003和vc6并没有真的理解不带BOM的utf-8文件,它们其实是当gbk的编码来理解的,往后看wchar_t的转码结果就能明白。

上面啰嗦了半天,结论就是,假如你在程序中写了中文,例如这样:

const char* p = "五道杠"; 

vc2005之后编译的结果将和当前codepage挂钩……而且将和gcc或vc2003的编译结果不一致……想搞跨平台?还是洗洗睡吧。

再来看wchar_t string literal,如果理解了char身上发生的事情,对照来看表格中wchar_t的结果倒是相当一致,大家都对源文件中的字串进行了转码(注意我没有提供任何UTF16格式的源文件),最终结果都是UTF16-LE编码。遗憾的是,在没有BOM的情况下,vc的编译器不能正确识别utf-8,所以它把“a好人”当成了“a濂戒汉”,由这4个字转换成UTF16-LE之后就变出了结果(5)

说到这里,可以看出wchar_t string literal也是个坑,更不要提ndk中的gcc是以4字节为1个wchar_t的长度,或许是按UTF32来进行编码的?以后有机会再试验吧。

最后总结一下,如果有潜在的跨平台需求,那是无论如何不能在代码中写ASCII以外的字符的。除非显式的使用0x1234, “\u1234″这种方式。另外,使用平台无关的字符串处理函数库代替wchar_t系的标准库函数也很有必要,例如ICU神马的,星际2里面就用的是它。对于vc这一系的编译器,其string literal的编译结果不仅和编译器版本相关,而且和编译器运行环境的codepage相关,着实是在给广大程序员添堵……

好长~以上= =b

4 thoughts on “为神马不要在代码里写中文(二)”

  1. 谢谢分享,写得很不错,问两个问题啊:
    1.你是在一台电脑上装了所有的VS吗?
    2.你是怎么知道星际2用的ICU的?
    THX。

  2. 1. 是的。
    2. 不记得在哪里看到的了,或者是看到了相关的dll引用。

  3. 你是用的什么系统啊?我现在安装两个都感觉有点怆啊,还有一个问题,你是用的电脑是什么配置?

Leave a Reply

Your email address will not be published. Required fields are marked *