[C/C++] 各种C/C++编译器对UTF-8源码文件的兼容性测试(VC、GCC、BCB)

在不同平台上开发C/C++程序时,为了避免源码文件乱码,得采用UTF-8编码来存储源码文件。但是很多编译器对UTF-8源码文件兼容性不佳,于是我做了一些测试,分析了最佳保存方案。

一、测试程序

  为了测试编译器对UTF-8源码文件兼容性,我编写了这样的一个测试程序——

//#if _MSC_VER >= 1600    // VC2010
//#pragma execution_character_set("utf-8")
//#endif

#include <stdio.h>
#include <locale.h>
#include <string.h>
#include <wchar.h>

char* psa = "\u4e00字A";
wchar_t* pdw = L"\u4e00字W";

int main(int argc, char* argv[])
{
    char* pa;
    wchar_t* pw;

    setlocale(LC_ALL, "");    // 使用系统当前代码页.

    // char
    printf("len<%d>=%d,str=%s\t//", sizeof(char), strlen(psa), psa);
    for(pa=psa; *pa!=0; ++pa)    printf(" %.2X", (unsigned char)*pa);
    printf("\n");

    // wchar_t
    printf("len<%d>=%d,str=%ls\t//", sizeof(wchar_t), wcslen(pdw), pdw);
    for(pw=pdw; *pw!=0; ++pw)    printf(" %.4X", (unsigned int)*pw);
    printf("\n");

    return 0;
}

 

  如果系统默认编码是GB2312(如中文Windows系统),该程序的输出结果应是——
len<1>=5,str=一字A // D2 BB D7 D6 41
len<2>=3,str=一字W // 4E00 5B57 0057

  如果系统默认编码是UTF-8(如Linux系统),该程序的输出结果应是——
len<1>=7,str=一字A // E4 B8 80 E5 AD 97 41
len<4>=3,str=一字W // 4E00 5B57 0057

  注:
1. “len”旁尖括号内的是字符类型的宽度。char类型一般是1字节。而wchar_t类型跟编译器与操作系统有关,Windows平台下一般2字节,Linux平台下一般4字节。
2. “len<?>=”右侧的数字是字符个数。用char类型,一个汉字的GB2312编码是2个字符,一个汉字的UTF-8编码一般是3个字符。而对于wchar_t类型,一个汉字一般是1个字符。
3. “str=”右侧的是所显示的字符串。
4. “//”右侧用于显示每一个字符的值。

二、测试结果

  需要测试这些方面——
1. 分别测试不同操作系统下的多种编译器。
2. 无签名的UTF-8与带签名的UTF-8。UTF-8存储方案分别有两种,一是无签名的UTF-8,另一是带签名的UTF-8,这两种方案的区别是——是否存在签名字符(BOM)。
3. 执行字符集。VC2010增加了“#pragma execution_character_set("utf-8")”,指示char的执行字符集是UTF-8编码。

  根据上面的要求,制定好了测试项目,分别有Window平台下的测试与Linux平台下的测试。
  Window平台下的测试有——
[VC6, noBOM]:VC6.0 sp1,源码使用无签名的UTF-8编码。
[VC6, BOM]:VC6.0 sp1,源码使用带签名的UTF-8编码。
[VC2003, noBOM]:VC2003 sp1,源码使用无签名的UTF-8编码。
[VC2003, BOM]:VC2003 sp1,源码使用带签名的UTF-8编码。
[VC2005, noBOM]:VC2005 sp1,源码使用无签名的UTF-8编码。
[VC2005, BOM]:VC2005 sp1,源码使用带签名的UTF-8编码。
[VC2010, noBOM]:VC2010 sp1,源码使用无签名的UTF-8编码。
[VC2010, BOM]:VC2010 sp1,源码使用带签名的UTF-8编码。
[VC2010, noBOM, execution_character_set]:VC2010 sp1,源码使用无签名的UTF-8编码,并使用“#pragma execution_character_set("utf-8")”。
[VC2010, BOM, execution_character_set]:VC2010 sp1,源码使用带签名的UTF-8编码,并使用“#pragma execution_character_set("utf-8")”。
[BCB6, noBOM]:Borland C++ Builder 6.0,源码使用无签名的UTF-8编码。
[BCB6, BOM]:Borland C++ Builder 6.0,源码使用带签名的UTF-8编码。
[gcc(mingw), noBOM]:MinGW中的GCC 4.6.2,源码使用无签名的UTF-8编码。
[gcc(mingw), BOM]:MinGW中的GCC 4.6.2,源码使用带签名的UTF-8编码。

  Linux平台下的测试有——
[gcc(fedora), noBOM, chs]:Fedora 17自带的GCC 4.7.0,源码使用无签名的UTF-8编码,系统语言设为“简体中文”。
[gcc(fedora), BOM, chs]:Fedora 17自带的GCC 4.7.0,源码使用带签名的UTF-8编码,系统语言设为“简体中文”。
[gcc(fedora), noBOM, eng]:Fedora 17自带的GCC 4.7.0,源码使用无签名的UTF-8编码,系统语言设为“英语”。
[gcc(fedora), BOM, eng]:Fedora 17自带的GCC 4.7.0,源码使用带签名的UTF-8编码,系统语言设为“英语”。

  测试结果汇总如下(分号“;”后的是我写的注释)——

[VC6, noBOM]
len<1>=9,str=u4e00瀛桝    // 75 34 65 30 30 E5 AD 97 41    ; VC6无法识别“\u”转义符,直接输出了“u4e00”。
len<2>=7,str=u4e00瀛梂    // 0075 0034 0065 0030 0030 701B 6882

[VC6, BOM]
无法编译!    ; 因BOM字符被编译器当做了错误的语句。

[VC2003, noBOM]
len<1>=0,str=    //    ; 编译器无法识别字符串。
len<2>=3,str=一瀛梂    // 4E00 701B 6882

[VC2003, BOM]
len<1>=0,str=    //
len<2>=3,str=一字W    // 4E00 5B57 0057

[VC2005, noBOM]
len<1>=6,str=一瀛桝    // D2 BB E5 AD 97 41
len<2>=3,str=一瀛梂    // 4E00 701B 6882

[VC2005, BOM]
len<1>=5,str=一字A    // D2 BB D7 D6 41
len<2>=3,str=一字W    // 4E00 5B57 0057

[VC2010, noBOM]
len<1>=6,str=一瀛桝    // D2 BB E5 AD 97 41    ; “字A”的UTF-8编码为“E5 AD 97 41”,编译器将它们识别为GB2312编码的“瀛桝”,并将其存储为GB2312字符串。
len<2>=3,str=一瀛梂    // 4E00 701B 6882    ; “字W”的UTF-8编码为“E5 AD 97 57”,编译器将它们识别为GB2312编码的“瀛梂”,并将其存储为UTF-16字符串。

[VC2010, BOM]
len<1>=5,str=一字A    // D2 BB D7 D6 41    ; 因带有BOM,编译器正确的识别了字符串,并将其存储为GB2312字符串。
len<2>=3,str=一字W    // 4E00 5B57 0057    ; 因带有BOM,编译器正确的识别了字符串,并将其存储为UTF-16字符串。

[VC2010, noBOM, execution_character_set]
len<1>=8,str=一鐎涙    // D2 BB E7 80 9B E6 A1 9D    ; “\u4e00”被识别为“一”,并存储为GB2312编码“D2 BB”。“字A”的UTF-8编码为“E5 AD 97 41”,编译器将它们识别为GB2312编码的“瀛桝”,并存储为UTF-8编码的“E7 80 9B E6 A1 9D”。但显示时系统默认是 GB2312 编码。
len<2>=3,str=一瀛梂    // 4E00 701B 6882

[VC2010, BOM, execution_character_set]
len<1>=6,str=一瀛桝    // D2 BB E5 AD 97 41    ; “\u4e00”被识别为“一”,并存储为GB2312编码“D2 BB”。“字A”的UTF-8编码为“E5 AD 97 41”,编译器正确的将其存储为UTF-8编码。但显示时系统默认是 GB2312 编码。
len<2>=3,str=一字W    // 4E00 5B57 0057

[BCB6, noBOM]
len<1>=6,str=一瀛桝    // D2 BB E5 AD 97 41
len<2>=3,str=一瀛梂    // 4E00 701B 6882

[BCB6, BOM]
无法编译!    ; 因BOM字符被编译器当做了错误的语句。

[gcc(mingw), noBOM]
len<1>=7,str=涓€瀛桝    // E4 B8 80 E5 AD 97 41    ; 存储为UTF-8编码。但显示时系统默认是 GB2312 编码。
len<2>=3,str=一字W    // 4E00 5B57 0057

[gcc(mingw), BOM]
len<1>=7,str=涓€瀛桝    // E4 B8 80 E5 AD 97 41
len<2>=3,str=一字W    // 4E00 5B57 0057

[gcc(fedora), noBOM, chs]
len<1>=7,str=一字A    // E4 B8 80 E5 AD 97 41    ; 存储为UTF-8编码。显示时系统默认是 zh_CN.utf8 编码,正常输出。
len<4>=3,str=一字W    // 4E00 5B57 0057

[gcc(fedora), BOM, chs]
len<1>=7,str=一字A    // E4 B8 80 E5 AD 97 41
len<4>=3,str=一字W    // 4E00 5B57 0057

[gcc(fedora), noBOM, eng]
len<1>=7,str=一字A    // E4 B8 80 E5 AD 97 41    ; 存储为UTF-8编码。显示时系统默认是 en_US.utf8 编码,正常输出。
len<4>=3,str=一字W    // 4E00 5B57 0057

[gcc(fedora), BOM, eng]
len<1>=7,str=一字A    // E4 B8 80 E5 AD 97 41
len<4>=3,str=一字W    // 4E00 5B57 0057

三、结果分析

  观察测试结果,我们首先可以发现以下几点——  
VC6和BCB6都无法编译带签名UTF-8编码的代码文件,它们会将签名字符(BOM)当做错误的语句。
VC6无法识别“\u”转义符。
VC2003无法识别UTF-8编码的char。

3.1 原理分析

  Windows下的测试以VC2010最为典型,以此为例来讲解。

  在编译过程中,处理字符串时会涉及下面两种字符集——
源码字符集(the source character set):源码文件是使用何种编码保存的。
执行字符集(the execution character set):可执行程序内保存的是何种编码。

  要想使程序不会乱码,必须满足——
1) 编译器准确识别了源码字符集,从而得到正确的字符串数据。
2) 运行环境的编码与执行字符集相同。运行环境的编码可通过setlocale函数来配置,“setlocale(LC_ALL, "")”表示使用系统默认编码。对于简体中文Windows来说一般是GB2312,如果执行字符集相同,那就能正常显示,否则会乱码。

  VC2010是这样处理的——
源码字符集:如果有签名字符,就按它的编码来解析;否则使用本地Locale字符集。
执行字符集:对于char类型,如果有“#pragma execution_character_set”,就按它的编码来存储字符串;否则使用本地Locale字符集。对于wchar_t类型,总是使用UTF-16编码。

  当源码使用带签名的UTF-8编码时,VC2010能正确的识别源码字符集是UTF-8。然后因没有“#pragma execution_character_set”,执行字符集是本地Locale字符集——
[VC2010, BOM]
len<1>=5,str=一字A // D2 BB D7 D6 41 ; 因带有BOM,编译器正确的识别了字符串,并将其存储为GB2312字符串。
len<2>=3,str=一字W // 4E00 5B57 0057 ; 因带有BOM,编译器正确的识别了字符串,并将其存储为UTF-16字符串。

  当源码使用无签名的UTF-8编码时,VS2010因找不到签名字符,源码字符集被误认为是本地Locale字符集。然后因没有“#pragma execution_character_set”,执行字符集是本地Locale字符集——
[VC2010, noBOM]
len<1>=6,str=一瀛桝 // D2 BB E5 AD 97 41 ; “字A”的UTF-8编码为“E5 AD 97 41”,编译器将它们识别为GB2312编码的“瀛桝”,并将其存储为GB2312字符串。
len<2>=3,str=一瀛梂 // 4E00 701B 6882 ; “字W”的UTF-8编码为“E5 AD 97 57”,编译器将它们识别为GB2312编码的“瀛梂”,并将其存储为UTF-16字符串。

  当使用“#pragma execution_character_set("utf-8")”配置了执行字符集为UTF-8后,情况变得更复杂了。我们先看看VC2010能正确识别源码字符集的带签名文件——
[VC2010, BOM, execution_character_set]
len<1>=6,str=一瀛桝 // D2 BB E5 AD 97 41 ; “\u4e00”被识别为“一”,并存储为GB2312编码“D2 BB”。“字A”的UTF-8编码为“E5 AD 97 41”,编译器正确的将其存储为UTF-8编码。但显示时系统默认是 GB2312 编码。
len<2>=3,str=一字W // 4E00 5B57 0057

  再看看无签名时的情况。VS2010因找不到签名字符,源码字符集被误认为是本地Locale字符集,即误将UTF-8识别为GB2312。然后根据执行字符集,又转换编码为UTF-8进行存储。最后在运行时因默认编码是GB2312,再次误将UTF-8识别为GB2312——
[VC2010, noBOM, execution_character_set]
len<1>=8,str=一鐎涙 // D2 BB E7 80 9B E6 A1 9D ; “\u4e00”被识别为“一”,并存储为GB2312编码“D2 BB”。“字A”的UTF-8编码为“E5 AD 97 41”,编译器将它们识别为GB2312编码的“瀛桝”,并存储为UTF-8编码的“E7 80 9B E6 A1 9D”。但显示时系统默认是 GB2312 编码。
len<2>=3,str=一瀛梂 // 4E00 701B 6882

  从上面这2个例子中,发现VC2010存在一个Bug——“#pragma execution_character_set”对“\u”转义字符无效,“\u”转义字符总是使用本地Locale字符集,而不是执行字符集。

3.2 GCC分析

  GCC的源码字符集与执行字符集默认是UTF-8编码,这是因为现在的Linux系统大多使用UTF-8编码。就算调整了Linux系统语言后,只是区域发生了变化,字符编码依然是UTF-8。所以我们的程序在“简体中文”与“英语”下,均能正确的显示中文字符。

  MinGW中的GCC也是这样的,源码字符集与执行字符集默认是UTF-8编码。但是简体中文的Windows的默认编码是GB2312,会将printf输出UTF-8字符串误认为是GB2312,造成乱码。

3.2 最佳方案

  如果字符串常量中没有非ASCII字符,建议源码文件使用无签名的UTF-8编码,这样能支持早期的编译器。
  如果字符串常量中含有非ASCII字符,建议源码文件使用带签名的UTF-8编码,这样能使大多数编译器正确的处理源码字符集。

  补充——
1. 注意条件仅是“字符串常量中没有非ASCII字符”。如果是从外部文件或其他途径获得非ASCII字符串,只要选择了合适的字符串函数,无签名UTF-8编码的源码文件也是能行的。
2. VC2010新增的“#pragma execution_character_set”用于明确要求UTF-8字符串的场合。由于Windows没有UTF-8的locale,实用性较小,

参考文献——
《ISO/IEC 9899:1999 (C99)》。ISO/IEC,1999。www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf
《C99标准》。yourtommy。http://blog.csdn.net/yourtommy/article/details/7495033
《QString乱谈(2) 》。dbzhang800。http://blog.csdn.net/dbzhang800/article/details/7540905

 

源码下载—— 
http://files.cnblogs.com/zyl910/testwchar.rar

时间: 2024-10-21 19:45:06

[C/C++] 各种C/C++编译器对UTF-8源码文件的兼容性测试(VC、GCC、BCB)的相关文章

编译器的工作过程

源码要运行,必须先转成二进制的机器码.这是编译器的任务. 比如,下面这段源码(假定文件名叫做test.c). #include <stdio.h> int main(void) { fputs("Hello, world!\n", stdout); return 0; } 要先用编译器处理一下,才能运行. $ gcc test.c $ ./a.out Hello, world! 对于复杂的项目,编译过程还必须分成三步. $ ./configure $ make $ make

编译器的工作过程(转)

码要运行,必须先转成二进制的机器码.这是编译器的任务. 比如,下面这段源码(假定文件名叫做test.c). #include <stdio.h> int main(void) { fputs("Hello, world!\n", stdout); return 0; } 要先用编译器处理一下,才能运行. $ gcc test.c $ ./a.out Hello, world! 对于复杂的项目,编译过程还必须分成三步. $ ./configure $ make $ make

java的包:库单元

我们用import关键字导入一个完整的库时,就会获得"包"(Package).例如: import java.util.*; 它的作用是导入完整的实用工具(Utility)库,该库属于标准Java开发工具包的一部分.由于Vector位于java.util里,所以现在要么指定完整名称"java.util.Vector"(可省略import语句),要么简单地指定一个"Vector"(因为import是默认的). 若想导入单独一个类,可在import语

对比C++和Java

"作为一名C++程序员,我们早已掌握了面向对象程序设计的基本概念,而且Java的语法无疑是非常熟悉的.事实上,Java本来就是从C++衍生出来的." 然而,C++和Java之间仍存在一些显著的差异.可以这样说,这些差异代表着技术的极大进步.一旦我们弄清楚了这些差异,就会理解为什么说Java是一种优秀的程序设计语言.本附录将引导大家认识用于区分Java和C++的一些重要特征. (1) 最大的障碍在于速度:解释过的Java要比C的执行速度慢上约20倍.无论什么都不能阻止Java语言进行编译

使用 Java 6 API分析源码

您可曾想过像 Checkstyle 或 FindBugs 这样的工具如何执行静态代码分析吗,或者像 NetBeans 或 Eclipse 这样的集成开发环境(Integrated Development Environments IDE)如何执行快速代码修复或 查找在代码中声明的字段的完全引用吗?在许多情况下,IDE 具有自己的 API 来解析源码并生成标准树 结构,称为 抽象语法树(Abstract Syntax Tree AST) 或"解析树",此树可用于对源码元素的进一步 分析.

Linux基本配置和管理 7 软件安装:源码和Tarball

一 开放源码的软件安装和升级简介 1 Linux上面的软件几乎都是经过GPL的授权,所以每个软件几乎均提供源代码,并且你可以自行修改 程序代码 2 在Linux系统上面,一个文件能不能被执行看的是有没有可执行的那个权限(具有x权限),不过 Linux上面真正识别的可执行文件其实是二进制文件,例如/usr/bin/passwd 3 程序代码文件其实就是一个一般的纯文本文件,在完成这个源码文件的编写之后,再来就是要将这 个文件"编译"成为操作系统看的懂的二进制文件,而要编译自然要写&quo

CLR笔记:1.CLR的执行模型

术语:CLR :Common Language Runtime 公共语言运行期,有多种不同编程语言使用的运行库 托管模块:Managed Module,一个标准的MS Window可移植执行体文件(32位PE32或64位PE32+) IL:Intermediate Language 中间语言,又叫托管代码(由CLR管理它的执行) 元数据:metadata,一系列特殊的数据表 程序集:Assembly,抽象的 JIT:just-in-time 即时编译,将IL编译成本地CPU指令(本地代码) FC

[Python爬虫] 中文编码问题:raw_input输入、文件读取、变量比较等str、unicode、utf-8转换问题

        最近研究搜索引擎.知识图谱和Python爬虫比较多,中文乱码问题再次浮现于眼前.虽然市面上讲述中文编码问题的文章数不胜数,同时以前我也讲述过PHP处理数据库服务器中文乱码问题,但是此处还是准备简单做下笔记.方便以后查阅和大家学习.        中文编码问题的处理核心都是--保证所有的编码方式一致即可,包括编译器.数据库.浏览器编码方式等,而Python通常的处理流程是将unicode作为中间转换码进行过渡.先将待处理字符串用unicode函数以正确的编码转换为Unicode码,

Go 环境变量

Go 开发环境依赖于一些操作系统环境变量,你最好在安装 Go 之间就已经设置好他们.如果你使用的是 Windows 的话,你完全不用进行手动设置,Go 将被默认安装在目录 c:/go 下.这里列举几个最为重要的环境变量: $GOROOT 表示 Go 在你的电脑上的安装位置,它的值一般都是 $HOME/go,当然,你也可以安装在别的地方. $GOARCH 表示目标机器的处理器架构,它的值可以是 386.amd64 或 arm. $GOOS 表示目标机器的操作系统,它的值可以是 darwin.fre