[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!=; ++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!=; ++pw) printf(" %.4X", (unsigned int)*pw);
printf("\n"); return ;
}

如果系统默认编码是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
[C/C++] 各种C/C++编译器对UTF-8源码文件的兼容性测试(VC、GCC、BCB)的更多相关文章
- C++开发时字符编码的选择
最近看了很多有关字符编码的讨论帖子, 自己也做了很多尝试, 针对linux和windows上字符编码的选择做了个简单整理, 在此做个记录 首先是基础编码知识, 下面我列出的4个编码方式或字符集是我们应 ...
- Crosstool-ng制作交叉编译工具链
Crosstool-ng制作交叉编译工具链 交叉编译器可以用现成的,比如CodeSourcery制作的交叉编译器,也可以自己制作,一般是用kernel+gcc+glibc+binutils的源码包来编 ...
- Eclipse编译运行没问题,但执行mvn clean install跑单元测试失败的原因解析
问题描述:mvn clean install编译工程并运行单元测试出现如下错误 Tests run: 3, Failures: 0, Errors: 2, Skipped: 0, Time elaps ...
- Zynq-7000 FreeRTOS(一)系统移植配置
软件版本:VIvado HLx 2018.2 从FreeRTOS的官网中下载源代码: https://www.freertos.org/a00104.html 图:FreeRTOS的官网 上图中,点击 ...
- go 学习笔记之详细说一说封装是怎么回事
关注公众号[雪之梦技术驿站]查看上篇文章 猜猜看go是不是面向对象语言?能不能面向对象编程? 虽然在上篇文章中,我们通过尝试性学习探索了 Go 语言中关于面向对象的相关概念,更确切的说是关于封装的基本 ...
- Nginx动静分离(Nginx+Tomcat)
第一步:nginx构建 第二步:Tomcat构建 1.Tomcat基础点 (1)Tomcat 是基于java开发的web容器,用来发布java代码和jsp网页. (2)开发人员开发java web网站 ...
- JVM常用命令(九)
前面东西说完后,现在可以说一些和我们平时进行性能调优相关的东西了,那怎么看和我们JVM性能调优相关的东西呢,其实这对我们开发来说是一个比较头痛的问题,其实我们JDK官网给了一些我们相关的指令,我们可以 ...
- 初学Java时没有理解的一些概念
背景 之前学Java属于赶鸭子上架,草草学习基础语法便直接做课程作业,许多概念问题仍不清楚,故在此梳理一下,主要参考廖雪峰和互联网资料. Java运行方式与JVM Java是介于编译型语言(C++)和 ...
- 04_Linux基础-.&..-cat-tac-重定向-EOF-Shell-more-ps-less-head-tail-sed-grep-which-whereis-PATH-bash-usr-locate-find
04_Linux基础-.&..-cat-tac->&>>-EOF-Shell-more-ps-less-head-tail-sed-grep-which-wherei ...
随机推荐
- Gvim 在进行文件对比时报cannot read or write temp files
本机环境为win7 64位旗舰版,gvim安装的是GVim7.4.解决办法如下: 在安装目录下有个"_vimrc"文件.修改19行.将 if &sh =~ '\<cm ...
- 如何使用虚拟机在U盘上安装linux
如何使用虚拟机在U盘上安装linux 将linux安装到U盘的方法有很多,我觉得用虚拟机还是很方便的,直接上干货 创建虚拟机 我用的vbox,vmware也一样.配置随意一点就好,配置高安装的也快. ...
- javascript组件开发之基类继承实现
上一篇文章大概的介绍了一下关于javascript组件的开发方式,这篇文章主要详细记一下基类的编写,这个基类主要是实现继承的功能 为什么要封装基类? 由于这次重构项目需要对各种组件进行封装,并且这些组 ...
- Kinect For Windows V2开发日志二:Kinect V2的基本参数
以下内容节选自Heresy的博客: 彩色影像:1920 x 1080 @ 30 / 15 FPS(根据环境亮度) 深度影像:512 x 424 @ 30 FPS.16bit 距离值(mm).可侦测 ...
- CF 335B - Palindrome 区间DP
335B - Palindrome 题目: 给出一个字符串(均有小写字母组成),如果有长度为100的回文子串,输出该子串.否则输出最长的回文子串. 分析: 虽然输入串的长度比较长,但是如果存在单个字母 ...
- Macbook之用brew安装Python
1. brew install python 2.If you don't have ~/.bash_profile, add ~/.bash_profile by touch ~/.bash_pro ...
- Windows删除大文件
Temp是目录 或者是 文件很大很大很大很大 cmd rd /s /q Temp
- iOS访问通讯录开发-读取联系人信息
读取通信录中的联系人一般的过程是先查找联系人记录,然后再访问记录的属性,属性又可以分为单值属性和多值属性.通过下面例子介绍联系人的查询,以及单值属性和多值属性的访问,还有读取联系人中的图片数据. 本案 ...
- Python 字典(Dictionary) setdefault()方法
描述 Python 字典(Dictionary) setdefault() 函数和get()方法类似, 如果键不已经存在于字典中,将会添加键并将值设为默认值. 语法 setdefault()方法语法: ...
- jQuery骨架
jQuery选择器 jQuery操作DOM jQuery中的事件与应用 jQuery的动画与特效 Ajax在jQuery中的应用 jQuery常用插件 jQuery UI插件 jQuery实用工具函数 ...