c语⾔字符映射表,字符集编码与CC++源⽂件字符编译乱弹
(收集转载)
最近在看国际化编程 (i18n:internationalization) 的东西,也弄清楚了点字符集有关的⼀些问题,其实⽹上的⼀些⽜⼈已经将字符集、Unicode 等相关的问题说的很清楚了,我在这⾥引⽤他们的总结并⾃⼰⼩结⼀下⼼得,并且实验⼀下在编译时,源代码⾃⾝的字符集与编译⽣成⼯具之间的问题。
locale与字符集
locale,中⽂有时翻译成“现场”,还不如叫英⽂的locale好,它的意思是“⼀套和地域有关的习惯⽽形成的程序运⾏上下⽂”,它由很多⽅⾯ (category) 组成,⽐如:某个地区的⼈们习惯怎样表⽰他们的货币⾦额 (LC_MONETARY) ,是⽤ "$100",还是⽤ "¥100";习惯怎么表⽰⼗进制多位数 (LC_NUMERIC) ,是每⼀千位进⾏分隔 "100,000",还是每⼀万位进⾏分隔 "10,0000";习惯怎么表⽰⽇期时间(LC_TIME) ,是⽇-⽉-年的⽅式 "30-01-1999",还是年-⽉-⽇的⽅式 "1999-01-30",等等还有其它⼀些⽅⾯,不过其中我们最关⼼的是⼀个叫LC_CTYPE的,CTYPE 的含义⼤概是:Character Type(字符类型),它表⽰某个地区的字符⽤哪个字符集进⾏编码。还有
LC_ALL,它是其它所有⽅⾯的并集。
C 标准库中设置 locale 的函数是:setlocale(),MSDN VC10 参考:Language and Country/Region Strings
字符集(Character-Set)按照发明顺序和继承关系,有以下常⽤的⼏种:
ASCII
ANSI 发布的字符编码标准,编码空间 0x00-0x7F,占⽤1个字节,上学时学的 C 语⾔书后⾯的字符表中就是它,因为使⽤这个字符集中的字符就已经可以编写 C 程序源代码了,所以给这个字符集起⼀个 locale 名叫 C,所有实现的 C 语⾔运⾏时和系统运⾏时,都应该有这个C locale,因为它是所有字符集中最⼩的⼀个,设置为其它 locale 时可能由于不存在⽽出错,但设置 C ⼀定不会出错,⽐如:当 Linux 的LANG 配置出错时,所有的 LC_* 变量就会被⾃动设置为最⼩的 C locale。MSDN VC10 参考:Code Pages。
ISO-8859-1
ISO 发布的字符编码标准,⼜称 Latin-1 字符集,编码空间 0x00-0xFF,占⽤1个字节,可以编码⼤多数的西欧地区语⾔。参考:ISO IEC 8859-1。
GB2312,GBK,GB18030
GB 系列是由中国国标局发布的字符编码⽅案(其中 GBK 不是正式标准),后期发布的版本兼容之前的,是之前的超集。
参考:中⽂的⼏个编码by blade2001
GB2312
为1-2字节变长编码,汉字区中编码 6763 个字符。
GBK
是微软对 GB2312 的扩展,后由国标局作为指导性标准,为1-2字节变长编码,编码 21886 个字符,分为汉字区和图形符号区。汉字区编码 21003 个字符,⽀持CJK汉字(简体、繁体、常⽤⽇韩⽂),Windows 代码页为 CP936。
GB18030
为1-2-4字节变长编码,汉字区编码 27484 个字符,⽀持CJK汉字、常⽤藏⽂、蒙⽂、维吾尔⽂等,Windows 代码页为 CP54936。
上⾯的所有编码都将ASCII作为⾃⼰的⼦集实现,所以这些字符集⼜叫做本地化的(Native)ANSI 字符集。
⼀般在程序中为了⽀持国际化,在程序初始化时,将 locale 设置为系统配置的 Native ANSI 字符集,即执⾏:setlocale(LC_ALL, "")。
ASCII、ISO-8859-1 这种⽤1个字节编码的字符集,叫做单字节字符集(SBCS- Single-Byte Character Set)。
GB 系列这种⽤1-2、4个不等字节编码的字符集,叫做多字节字符集(MBCS- Multi-Byte Character Set)。
由 SBCS 编码的数据可以随机访问,从任意字节偏移开始解编码都能保证解析出的字符和后继字符是正确的。
⽽由MBCS编码的数据,只能将其作为字节流进⾏解析,如果从随机任意的字节偏移开始解编码,有可能定位到切断⼀个字符的中间位置,导致后继解析出的字符连续出错。作为字节流时,是从某个标识位置进⾏解析字符,⽐如从数据的开始位置,或从每个新⾏符 '\n' 之后开始解析字符。
Unicode 的理解
⾸先,Unicode 它不是⼀个东西,它⾄少涉及3个⽅⾯:Code Point,UCS,UTF。
每个地区、国家都有⾃⼰的 Native ANSI 字符集,虽然它们在 ASCII ⼦集部分是相同的,但其它部分都不尽相同,如果⼀个字符仅在⼀个特定的Native ANSI 字符集中编码,那么好,如果⽤户使⽤别的字符集,那么⽆论如何也⽆法解析和表现这个字符。怎样让你的⽂本数据同时可以包含英、法、德、中、⽇、阿拉伯甚⾄世界上所有可能的、完全的字符?办法似乎只有⼀个,就是在世界范围内对所有可能的字符进⾏穷举编码,这就是最初提出Unicode 的原因,全称为Universal Multiple-Octet Code。
Code Point
在实际编码之前先给每个穷举到的字符指定⼀个序号,叫它Code Point,把它当做是数学概念,和⽤⼏个字节存储⽆关,只要发布Unicode 的标准化组织(ISO 和 )愿意,将新出现的字符继续向后编号就可以了,既然数学序号,就没有什么不够⽤的问题。编号时有⼀些原则,就是越常⽤的字符越靠前,编号到⼀定数量后,发现差不多了,常⽤字符都编完了,截⽌于此将之前的编号组成的⼦集叫做基本多⽂种平⾯(BMP- Basic Multilingual Plane),在 BMP ⾥的字符,只要4位16进制数就可以表⽰,当然在 BMP 以外的字符则需要使⽤5位或更多16进制数表⽰。⽐如:"汉" 字在 BMP ⾥,它的 Code Point 可以表⽰为 U+6C49。常⽤CJK汉字都落在 BMP 内,所以都能⽤U+HHHH的形式表⽰其 Code Point。Windows 下有个字符映射表()的⼯具,可以列举每个字符的Code Point、字体⽀持、字符集之间的关系。
UCS
有了 Code Point 后就可以规定它的字符集,叫做UCS- Unicode Character Set,它和存储有关,⽤2个字节存储 Code Point 叫做UCS-2,⽤4个字节存储的叫做UCS-4,UCS-2 可以编码并存储 BMP 中的所有字符,⽽如果不够⽤了(要⽤到 BMP 外的字符),则可以使⽤ UCS-4。通常交流中提到 Unicode,如果不特指,就指代的是 UCS-2。
UCS 和 Native ANSI 字符集采⽤的 MBCS 编码是不同的,UCS 不将 ASCII 作为⾃⼰的⼦集,⽆论什么情况 UCS 总使⽤定长的字节来编码字符,UCS-2 使⽤2个字节,UCS-4 使⽤4个字节,⽽不是 Native ANSI 字符集中可能采⽤的变长编码。⽐如:"A" 在 GB2312 中编码为 0x41,⽽在 UCS-2 中编码为 0x0041。
当存储多字节编码的数据并且不将其作为字节流解析时,就要考虑保存数据的⼤⼩端问题(Endian),可以使⽤BOM(Byte Order Mark)标识⼀个 UCS 字符数据块是采⽤ Big Endian 还是 Little Endian 进⾏存储:在 Unicode 概念中有⼀个字符,它的 Code Point 为
U+FEFF,实际上它不映射到任何地区、国家中的可能字符,即是⼀个不可能存在字符的 Code Point((-_-^),Unicode 标准对它的注释为:ZERO WIDTH NO-BREAK SPACE),当开始处理 UCS 数据块时,UCS 标准建议先处理这个 ZERO WIDTH NO-BREAK SPACE 字符,⽐如 UCS-2 数据块,
如果⼀开始读到/写⼊的字节序列是 FF FE(8 进制:377 376),那么说明后续的 UCS-2 按 Little Endian 存储;如果是 FE FF(8进制:376 377),则说明后续的 UCS-2 按 Big Endian 存储。
采⽤定长的 UCS 有⼀个好处,就是可以像 SBCS ⼀样随机访问数据块中的任何字符,当然这⾥的随机偏移单位不是每字节:当⽤ UCS-2,是每2字节随机偏移,当⽤ UCS-4 时,是每4字节随机偏移。
但是 UCS 也有缺点,⼀是有些浪费:⽐如⽤ UCS-2,如果在⼀个数据块中只使⽤对应于 ASCII 中的字符,那么有⼀半存储都被浪费掉了,因为对应于ASCII 中的字符,它的 UCS-2 编码实际上是它的 ASCII 编码加上填0的⾼1字节组成的2字节编码,那种使⽤16进制编辑器打开⽂件后隔⼀列为0的字符⽂件就是这种情况。⼆是和 ASCII 不兼容,由于太多的已有系统使⽤ ASCII(或 Native ANSI)了,这点使UCS 和其它系统对接时有点⿇烦。
UTF
UTF - Unicode Transformation Format,作为 Unicode 的传输编码,是对 UCS 再次编码映射得到的字符集,能够⼀定程度上解决上⾯ UCS 的2个缺点。UTF-8 是以8位为单元对 UCS-2 进⾏再次编码映射,是当前⽹络传输、存储优选的字符集。UTF-8 使⽤8位单元(1字节)变长编码,并将 ASCII 作为⼦集,这样就可以将 UTF-8 当做⼀种 MBCS 的 Native ANSI 字符集的实现,因此 UTF-8 需要使⽤1字节流⽅式解析字符。处于BMP 中的CJK汉字,使⽤ UTF-8 编码时通常会映射到3字节序列,⽽ GB 系
列字符集中的CJK汉字通常为2字节序列。
UTF-8 和所有的 Native ANSI 字符集⼀样:当数据块中只有 ASCII ⼦集部分的字符时,是⽆法区分这个数据块⽤哪种 Native ANSI 字符集进⾏编码的,因为这部分的编码映射关系对于所有的 Native ANSI 字符集是共享的,只有当未来数据块中包含像CJK汉字这种在 ASCII ⼦集之外的字符时,采⽤不同 Native ANSI 字符集的数据块才会表现出不同。
不过有⼀种⽅法可以让数据块标识⾃⼰使⽤的是 UTF-8 编码(即使字符内容都在 ASCII 内),这对于⽂本编辑器等应⽤很有⽤,它们可以使⽤这个标识判断⽂件当前使⽤的字符集,以便未来插⼊ ASCII 之外的字符时决定如何编码。这个标识⽅法就是使⽤ UCS-2 中 BOM 的UTF-8 编码,其1字节流为:EF BB BF(8 进制:357 273 277)。当数据块的开始有这个流时就说明后续字符采⽤ UTF-8 编码。因为UTF-8 使⽤1字节流⽅式处理,这时 BOM 已经失去其在 UCS-2 中作为标识字节序⼤⼩端的作⽤,⽽仅把 EF BB BF 作为 UTF-8 编码的标识功能(Magic),有时就叫它UTF-8 Signature。但并⾮所有能处理 UTF-8 数据的应⽤都假定有 Signature 这个标识功能的存在:微软的应⽤⼤多都⽀持 UTF-8 Signature,但在开源领域,⽐如 Linux 下有相当多的程序都不⽀持 UTF-8 Signature。
源⽂件字符集与编译
在 ISO C99 中有了宽字符处理的标准,例程⼤多在wchar.h中声明,并且有了wchar_t这么⼀个类型。
不管哪种 C 编译器和标准/RT库实现,wchar_t 通常都可以认为是存储 UCS 字符的类型,C 语⾔语法中也使⽤前缀的L字符来说明⼀个字符常量、字符串字⾯量在编译时采⽤ UCS 编码。
VC8 cl的实现中,默认的编译选项将 wchar_t 做为内建类型(选项:/Zc:wchar_t),此时 sizeof(wchar_t) 为 2,可存储 UCS-2 编码。Linux GCC 4 的实现中,sizeof(wchar_t) 为 4,可存储 UCS-4 编码。MinGW 和 Cygwin 的 GCC 4 中,sizeof(wchar_t) 为 2。
如此有这么⼀个疑问:
源⽂件程序语法中的字符编码指⽰。
源⽂件⾃⾝的字符编码。
这2者有何种联系?于是我做了如下实验,试着搞明⽩编译器对上⾯2者的处理作⽤。分别实验了3个我常⽤的编译⼯具集:VC8、MinGW GCC、Linux GCC。
先看当程序语法中使⽤⾮ wchar_t 字符编码指⽰时的情况,按照教科书上的说法这种字符串字⾯量编译时使⽤ ASCII 编码,因为其中有汉字,因此我把它想象成⽤某种 Native ANSI 字符集进⾏编码,在随后的测试和调试中便可判断这种假定是否正确。
源码如下:
01
#include "common.h"
02
03
#define MAX_BUF_SIZE    256
04
typedefunsignedcharBYTE;
05
06
constcharg_szZhong[] ="这是ABC 123汉字";
07
08
intmain(intargc,char* argv[])
09
{
10
BYTEbuf[MAX_BUF_SIZE] = {0};
11
FILE* fs = NULL;
12
if( argc < 2 )
13
{return1;    }
14
15
if( (fs =fopen(argv[1],"wb")) == NULL )
16
{returnerrno;    }
17
18
memcpy(buf, g_szZhong,sizeof(g_szZhong));
19
20
fwrite(buf,sizeof(char),sizeof(g_szZhong), fs);
21
fclose(fs);
22
23
returnerrno;
24
}
使⽤记事本、iconv 等⼯具将上⾯的源⽂件做出5份不同的字符集编码的出来:GBK、UCS-2 LE、UCS-2 LE(BOM)、UTF-8、UTF-
8(BOM)。其中 LE 表⽰UCS-2 采⽤ Little Endian 字节序存储;带 BOM 的表⽰:在⽂件头有 BOM 标识,对于 UTF-8 来说就是Signature,没有带 BOM 的就没有这个⽂件头标识。
我编译⽣成了上⾯的程序后,查看了3处字符串的编码:
内存中的字符串:使⽤ gdb、VC 等调试⼯具,跟踪 memcpy() 时向 buf[] 中复制的字符数据。
可执⾏⽂件中的字串常量:使⽤ WinHex、hd 等16进制查看⼯具,在编译⽣成的对象⽂件和可执⾏映像⽂件中查字符串字⾯量的中间部分 "ABC 123"。
该程序写⼊的⽂件:该程序使⽤ fwrite() 向某个命令⾏参数指定的⽂件写⼊ buf[] 中的字符数据,查看这个写⼊⽂件的编码。
实验结果:
MinGW GCC 4.4.0
源代码
内存中的字符串
可执⾏⽂件中的字串常量
写⼊的⽂件
GBK
GBK
GBK
GBK
UCS-2 LE (BOM)
编译出错
UCS-2 LE
编译出错
UTF-8 (BOM)
UTF-8
UTF-8
UTF-8
UTF-8
UTF-8
UTF-8
UTF-8
Linux GCC 4.3.2
源代码
内存中的字符串java语言使用的字符码集是
可执⾏⽂件中的字串常量
写⼊的⽂件
GBK
GBK
GBK
GBK
UCS-2 LE (BOM)
编译出错:不识别 BOM (FF FE),且源代码字符处理出错UCS-2 LE
编译出错:源代码字符处理出错
UTF-8 (BOM)
编译出错:不识别 BOM (EF BB BF)
UTF-8

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