vc对宽字符的预处理
这个问题一开始源于用vc编译一个utf8文件, 遇到了错误error C2001: 常量中有换行符
原本是个mingw下写的程序, gcc编译时没问题的, 后来网上搜了下, 说是因为vc对无bom的utf8文件支持不太好, 得加上bom才能正常编译
在vc上面测试以下代码
#include <stdio.h> #include <Windows.h> int main(int argc, char ** agrv) { unsigned char *ptr = (unsigned char *) TEXT("测试"); int i; for (i = 0; i < sizeof(TEXT("测试")) / sizeof(char); ++i) printf("%02x ", *(ptr + i)); return 0; }
分别将源码改为utf8 with bom, gbk, gb2312, gb18030, utf16-le, utf16-be…..
发现无论源代码自身是什么字符集, 如果使用UNICODE方式, 输出内容都为4b 6d d5 8b 00 00, 而如果使用MBCS方式, 输出的内容都为b2 e2 ca d4 00
可以总结出msvc对字符串预处理的规律:
无视源码本身字符集, 使用UNICODE方式编译时一律转换为unicode(ucs-2)
采用MBCS则一律转换为系统当前代码页的字符集, 这里也就是gbk, 代码页936 (简体中文系统).
但这个规律对utf8 without bom却又不适用
顺便说下windows, java, qt里面字符串按照unicode编码存储, 其实并不是完整的unicode
完整的unicode需要4个字节, 即ucs-4, 而2个字节的是ucs-2, 它已经能满足大部分场合的需求了
ucs-2表示范围内的字符刚好可以由一个utf-16单元来编码, 所以utf-16大部分时候和ucs-2是等同的, 但用utf-16去编码ucs-2范围之外(ucs-4中超出ucs-2能表示的范围)的字符时就不能等同了, 此外utf-16需要考虑不同机器上的字节序, 所以有le/be
还有一点发现, windows虽然是小端序, 但是内存中字符串却是按照大端序存储的:
“测”和”试”的gbk编码值分别是b2e2, cad4, 即其对应unsigned short数值分别是b2e2, cad4, 如果直接按系统的小端序存储, 那打印的结果就应该是e2 b2 d4 ca, 实际却和这个相反. unicode也是如此
最后测试encoding使用utf8 without bom的源码
使用MBCS方式编译, 输出结果是: e6 b5 8b e8 af 95 00, 这是”测试”这两个汉字的utf-8编码, 也就是说编译器直接使用源文件里面该字符串的位模式值, 并没有转换为gbk(却会被当作gbk来解析, ui中就会出现乱码)
如果选择UNICODE方式, 则输出: 34 5a 2d 5b 2f 76 00 00, 这又是什么, 是ascii字符串”4Z-[/v”吗?
no, 其实是编译器把源代码里面的”测试”的utf8位模式当作了gbk来解析, 再转换为unicode(utf16-be)
e6 b5 8b e8 af 95按照gbk编码解析, 对应的汉字是”娴嬭瘯”, 而这三个汉字的unicode值是5a34, 5b2d, 762f(打印的是34 5a 2d 5b 2f 76, 不知道为什么这里又倒过去了…目前没想明白)
结论就是不要用vc编译字符串内容包含非ascii码的无bom utf8源文件, 有可能编译通不过, 即便通过也是乱码而不是我们想要的字符串