我们传统的程序基本都只在Windows或只在Linux下运行,Windows程序使用简体中文GB18030编码,Linux程序则只使用英文,多年以来这些程序运行起来都没有问题。
近年来,随着程序的组件化,部分代码特别是公用组件都需要同时支持Windows及Linux平台,这样就出现了不同程度的编码问题,例如在编译时编译器报错,或者在运行时出现乱码。这些问题都和程序选用的字符编码不正确有关。
本文简要地分析了C++的一些字符编码问题,并提供了建议的方案。受经验和时间的限制,有些内容可能不一定全面,仅供大家参考。
1. C++源文件的编码需要特别考虑吗?
1.1. 几个相关概念
首先要区分几个概念:
C++源文件的编码
指的是C++源程序文件(。cpp/.h)本身使用什么字符编码(GB18030/UTF-8等)。
C++程序的内码
编译后,C++中的字符串常量都会变成一串字节存放在可执行文件中。这个内码指的就是在可执行文件中,字符串以什么编码进行存放。这里的字符串常量指的是窄字符(char)而非宽字符(wchar_t)。宽字符通常是以Unicode(VC使用UTF-16BE,gcc使用UTF-32BE)存放。
运行环境编码
指的是执行程序时,操作系统或终端所使用的编码。程序中输出的字符最终要转换为运行环境编码才能正确显示,否则就会出现乱码。
1.2. 各种环境下通常使用的编码
C++源文件的编码
通常在简体中文Windows环境下,各种编辑器(包括Visual Studio)新建文件的缺省编码都是GB18030,所以不特别指定的话,Windows环境下C++源文件的编码通常为GB18030。而在Linux环境下,最常使用,也是推荐使用的是UTF-8编码。
C++程序的内码
一般来说,我们常用的简体中文版VC所使用的内码是GB18030,而gcc/g++使用的内码缺省是utf-8,但可以通过-fexec-charset参数进行修改。Note 可以通过在程序中打印字符串每个字节十六进制形式来判断程序所使用的内码。
运行环境编码
我们常用的简体中文版Windows的环境编码是GB18030,而Linux下最常用的环境编码是UTF-8。
1.3. 这几个编码之间的关系
源程序需要由编译器编译为目标文件,目标文件运行后输出信息到终端,因此这几个编码之间存在一些的关联:
unicode码和ascii码区别+--------+ | 源程序|----------源文件编码+---+----+ | 编译器编译+---+----+ |目标文件
|----------程序内码+---+----+ | 运行后输出信息+---+----+ | 输出|----------运行环境编码
+--------+
编译器需要正确识别源文件的编码,把源文件编译为目标文件,并把源文件中的以源文件编码的字符串转换为以程序内码编制的字符串保存在目标文件中。
Note 当源文件的字符编码与程序内码都是UTF-8时(gcc的缺省情况),gcc似乎并不会对源文件中的字符编码进行转换,而是直接把字符串原样存放到目标文件中,在这种情况下,源程序中的GB18030编码的字符串在输出时仍然为GB18030编码。但如果在其它源文件字符编码的实际值与编译选项不同时,会在编译时报无法从XXX转换到UTF-8的错,因此还不清楚为什么两个编码都是UTF-8时,GB18030 编码的源文件能通过编译。C++标准库需要正确识别终端的运行环境编码,并把程序的输出转换为运行环境所使用的编码,以便正确显示。
在这过程中,如果有一个环节出现问题,就会导致程序的输出发生异常,产生乱码或其它更严重的后果。
2. 源文件应该采用什么编码?
2.1. 编译器对不同源文件编码的支持一样吗?
gcc/vc各版本对C++源文件编码有不同的处理:
gcc (v4.3.2 20081105):
支持UTF-8编码的源文件,UTF-8编码的源文件不能有BOM。
开始支持带BOM的UTF-8文件。
vc2003:
支持UTF-8编码的源文件,UTF-8编码的源文件可以有BOM,也可以没有。
vc2005+:
如果源文件使用UTF-8编码的话,必须有BOM。Note gcc提供了-finput-charset参数可以指定源文件的字符编码,但由于标准头文件都是ascii编码的,因此如果要引用标准头文件的话,源代码的编码必须兼容ascii。而vc未能到类似的选项。
2.2. 源文件应该采用什么编码?
很多文章都推荐C/C++代码中只使用ascii字符,如果有非ascii字符可以用\xHH
或\uXXXX表示。注释中建议使用utf-8编码。也可以使用gettext 把非ascii字符串放到单独的语言文件中,而在源代码中只保留ascii字符。
在实践中,由于\xHH或\uXXXX等方式很不直观,容易出错且不易发现,而未必所有程序都需要支持多语言,因此未必想引入gettext或类似的解决方案。在这样的情况下,大家都习惯在源程序文件中直接写入中文等非ascii字符,这就需要选择一种至少能被gcc和vc接受的文件编码。本来,Unicode是解决多语言问题的最好选择,而UTF-8由于与ASCII 兼容,也是最通用的Unicode编码方式,但从上面的资料中可见,如果用UTF-8的话,gcc(至少是低版本)不允许有BOM,而vc2005 以上要求必须有BOM,因此同一个文件无法在gcc 及vc下通过编译,UTF-8似乎不是一个好的选择。但如果使用gcc比较高的版本(4.4.0以上?),使用带BOM的UTF-8编码文件应该也是可行的。
考虑到目前现状,我们一般都在简体中文Windows下工作,源文件中使用GB18030=编码似乎是一个比较现实的选择。在vc下可以直接编译,而在gcc下也可以通过增加编译选项-finput-charset=gb18030予以支持。而且根据中GB18030的词条内容,GB18030 is a superset of ASCII and can represent the whole range of Unicode code points(GB18030向后
兼容ASCII,并且能表示所有的Unicode码点),因此使用GB18030有足够的表达能力,可以表示所有的Unicode字符。使用GB18030的唯一缺点就是在非简体中文版本的VC下,由于无法指定源文件的编码,因此有可能无法正确识别此编码的源文件。
3. 应该使用什么程序内码?
正如前面提到的,C++有窄字符(char)和宽字符(wchar_t)的分别,分别有一套相应的类和函数(string/cout/strlen与wstring/wcout/wcslen等)。前者在不同的编译器下有不同的缺省编码(简体中文vc是GB18030,gcc是UTF-8),后者一般都使用Unicode,其中vc下使用UTF-16,
gcc缺省使用UTF-32。C++在输出窄字符时会按程序内码原样输出,不会进行编码转换,因此在使用窄字符时要求程序内码与运行环境编码一致,这样才不会出现乱码。由于简体中文版vc的程序内码是GB18030,因此使用窄字符的vc程序只能运行在GB18030环境下。同样,由于gcc缺省使用UTF-8作为程序内码,因此使用窄字符的gcc程序只能运行在UTF-8的终端环境下。(这里说的都是在源代码中直接写中文等非ascii字符的程序。用前面提到的gettext及其它工具,使用窄字符的程序也可以在不同编码的运行环境中正确输出中文)
C++在输出宽字符时会自动转换为运行环境的编码,因此只要正确设置了运行环境编码,同一个程序就可以在不同编码的运行环境中正确显示中文。这一点与Java/.Net很象,Java/.Net的字符串类型都使用Unicode,在输入/输出时都需要与当前运行环境的编码进行互转。
一般来说,如果需要支持多语言,有两种比较好的做法:
使用窄字符,但源程序中只使用ascii字符,非ascii字符通过gettext或其它
工具放到单独的文件中,由gettext等工具处理编码转换的问题。
在各种编码的运行环境中均能正确输出中文。
程序中不能直接出现非ascii字符,也不能通过\uXXXX方式指定非ascii字符,后者也会被编译器转换为非ascii字符并存放在目标文件中。
注释中可以使用ascii兼容的编码,不影响编译器。
有比较多的现成代码可供重用。
使用宽字符。
在各种编码的运行环境中均能正确输出中文。
程序中可以使用非ascii字符。
需要配合前面的源程序文件编码设置,让编译器能正确识别源程序中的非
ascii字符。
由于以前使用宽字符的程序比较少,可供重用的代码较少。
Note 如果程序中需要一些固定字符编码的字符串常量,例如固定是GB18030
编码的字符串常量,这些常量应该以\xXX的方式存放字符串常量经GB18030编码后的内容,这样的内容才不会被转换为程序的内码,也不会转换为运行环境编码。
4. 运行环境应该用什么字符编码?
正如上面提到的,使用窄字符和使用宽字符的程序对运行环境的字符编码要求是
不一样的。
使用宽字符,只要在程序中正确设置当前环境的字符编码(一般通过
locale::global(locale("")) 进行设置),C++标准库会在输入、输出时正
确进行字符编码转换,因此可以适应各种编码的运行环境。
使用窄字符,但程序中不出现非ascii字符的话,对运行环境没有特别要求,
可以适应各种编码的运行环境。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。