原创文章,欢迎转载。转载请注明:转载自 祥的博客
原文链接:https://cloud.tencent.com/developer/article/1596368
- C 中文问题
- 例子
- GCC
- MSVC
- 怎么办?
- UTF-8方案
- C 11
- 有个问题?
- C 11执行字符集
- 我的理解和总结
- 参考文章
转载文章,对于文章稍加编辑,文后有原文链接。
长期以来,很多人都清楚,一旦C 源码中直接使用了中文,这样的源码想要跨平台(I18N)会非常困难。
随着:
- Windows下:MSVC2010成为主流
- Linux下:GCC升级到4.6
C 中的中文问题 才算有了一个比较优雅的、跨平台的Workaround。
(本文讨论编译器范围:GCC4.6 , MSVC2010sp1 。本文属于QString系列,但暂不涉及QString)
C 中文问题
要在C 中正确使用中文,必须要了解下面两个概念:
字符集 | 解释 |
---|---|
源码字符集(the source character set) | 源码文件是使用何种编码保存的 |
执行字符集(the execution character set) | 可执行程序内保存的是何种编码(程序执行时内存中字符串编码) |
C 98的问题: 既没有规定源码字符集,也没有规定执行字符集
这个… 如何理解?不妨看个例子。
例子
这个要求高么?
- 一个简单的C 程序,只是希望它能在简体中文Windows、正体中文Windows、英文版Windows、Linux、MAC OS…下的结果一致。
//main.cpp
int main()
{
char mystr[] = "老老实实的学问,来不得半点马虎";
return sizeof mystr;
}
可以试着反问自己两个问题:
- 这个源码文件是何种编码保存的?(有确定答案么?)
- mystr中是什么内容?(有确定答案么?)
对C 来说,这两个都不确定。
- 固定平台的话,还能忍忍
- 要跨平台的话,这种东西…
GCC
在GCC下,这两个都可以使用你自己喜好的编码(如果不指定,默认都是UTF8)
代码语言:javascript复制-finput-charset=charset
-fexec-charset=charset
除了前两个选项外,还有一个:
代码语言:javascript复制-fwide-exec-charset=charset
wide? 不妨先猜一下它是干嘛的
MSVC
MSVC没有类似前面的选项。
问题 | 解释 |
---|---|
源码字符集如何解决? | 有BOM么,有则按BOM解释,无则使用本地Locale字符集(随系统设置而变) |
执行字符集如何解决? | 使用本地Locale字符集(随系统设置而变) |
挺霸道哈(当然,源码中可以使用#pragma setlocale("...")
,但功能很有限,比如Windows没有utf8
的locale
,所以…)。
另外,和GCC
对应的wide-exec-charset
呢?
问题 | 解释 |
---|---|
宽执行字符集如何解决? | 不妨先考虑一下 |
怎么办?
这才两个编译器,看起来就这么复杂了。而C 编译器的数目远大于2.
要想跨平台,必须确保这两个字符集都是“确定”的,而能胜任该任务的字符集,似乎理想的也只能是…
UTF-8方案
如果我们将源码保存成UTF-8
,执行字符集也选为UTF-8
,那么天下将太平了。使用非ASCII字符
的源码文件也就可以在不同国家的用户间无障碍流通了 :)
。
源码
保存成UTF-8
没有什么困难,但是,执行字符集需要是UTF-8
。没那么简单!
对GCC
来说,这个问题很简单(默认的编码选项足够了):
- 只要源码文件保存成
UTF-8
即可(带或不带BOM
均可) - 早期的
GCC
不接收带BOM
的UTF-8
源码文件,现在,至少在GCC4.6
中,这一限制不再存在。
对MSVC
来说,这个问题异常复杂:
- 对
MSVC2003
来说,只要源码保存成不带BOM
的UTF-8
即可 - 对
MSVC2005
、(没在SP1
基础上装热补丁的)MSVC2008
来说。完全没办法 - 直到
MSVC2010 sp1
,才算提供了一个解决方案。源码保存成带BOM
的utf-8
,utf-16
,…,然后添加
#pragma execution_character_set("utf-8")
要想跨GCC4.6
和MSVC2010 sp1
,我们需要取它们的交集:
也就是
- 源码保存成带
BOM
的utf-8
- 为MSVC添加
#pragma
//main.cpp
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
int main()
{
char mystr[] = "老老实实的学问,来不得半点马虎";
return sizeof mystr;
}
C 11
等到MSVC
支持C 11
的String Literals
之时,我们就没必要用那个蹩脚的pragma
了,直接
char mystr[] = u8"老老实实的学问,来不得半点马虎";
即可(尽管现在在GCC
下没问题,但要跨平台,估计要等到Visual C 12
了)。
有个问题?
C 98
中不是有个wchar_t
么,它不是用来表示unicode字符的么?
Unicode 4.0标准的5.2节是如何说的:
The width of wchar_t is compiler-specific and can be as small as 8 bits. Consequently, programs that need to be portable across any C or C compilershould not use wchar_t for storing Unicode text. The wchar_t type is intended forstoring compiler-defined wide characters, which may be Unicode characters in some compilers.
在回头看看GCC的选项
代码语言:javascript复制-fwide-exec-charset=charset
尽管GCC为其提供的默认编码是UTF16
或UTF32
(取决于wchar_t
的宽度),但该编码是可以随意设置的。
尽管这个东西不保证跨平台,也很不好玩, 但是,由于在windows
下面wchar_t
用来表示utf16
字符,而且直接对应系统API
接口,所以在类型char16_t
普及之前,还是很重要的。
C 11执行字符集
前面提到的u8
就是C 11
为“执行字符集”所做的努力之一。
新明确规定了utf8
、utf16
和utf32
这3种执行字符集。
char* | u8”中文” |
---|---|
char16_t* | u”中文” |
char32_t* | U”中文” |
可是C 11并没有规定源码字符集
代码语言:javascript复制const char* mystr=u8"中文";
C 标准对编译器说,我不管这个文件的具体编码是什么,但你必须给我生成对应utf8编码的字节流。
编译器似乎有点傻了吧?不知道源文件的编码,我如何转换
于是:
- MSVC说:源码文件必须有BOM,不然我就认为你是本地locale的编码
- GCC说:我认为你就是utf8编码,除非通过命令行通知我其他编码
- 在C 11标准下,对源码编码 简单的处理办法还是,使用带BOM的UTF8保存。
我的理解和总结
我一般的开发环境为 WIn7 64bit
VS2013
Qt5.5
。
一般VS2013
默认保存源文件的编码格式为GB2312
,所以对于Qt
的中文显示问题,屡试不爽的方法就是,在文件中加入:
#pragma execution_character_set("utf-8")
但是,用Git
进行版本控制的时候,就可以看到注释中的乱码。所以最好的方法还是将源文件转换为UTF-8
的格式,至于加不加BOM
,打算在下一篇博文进行探究。
参考文章
- QString乱谈(2) : https://blog.csdn.net/dbzhang800/article/details/7540905
- http://gcc.gnu.org/onlinedocs/cpp/Character-sets.html#Character-sets
- http://support.microsoft.com/kb/980263