ANSI C读书笔记系列之字符集篇----第四章 字节序

"endian"这个词出自<<格列佛游记>>,小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开.我们一般将endian翻译成"字节序",将big endian和little endian称作"大端"和"小端".

在计算机科学领域中,字节序是指存放多字节数据的字节的顺序,典型的情况是整数在内存中的存放方式和网络传输的传输顺序.既然我们在本篇介绍字节序,那所说的重点则其一是字符(多字节)在内存中的字节存放顺序(这与前面的典型情况是同一类),其二是字符(多字节)在文件中保存的编码顺序.

一般谈到字节序问题,都是指上面说的典型情况,主要是处理内存中数据.这里的字节序跟CPU相关,主要涉及两大派系:Motorola系列和Intel的x86系列.Motorola采用big endian方式存储数据,而x86系列则采用little endian方式存储数据,当然,还有ARM等可配置"大端""小端"CPU系列.

big endian是指低地址存放最高有效字节,而little endian则是低地址存放最低有效字节. 如数字0x12345678在两种不同字节序CPU中的存储顺序如下所示:

Big Endian

                       低地址                                            高地址
                          ----------------------------------------->
                     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                    |     12     |      34    |     56      |     78    |
                     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Little Endian

                        低地址                                            高地址
                           ----------------------------------------->
                     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                    |     78     |      56    |     34      |     12    |
                     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

从上面两图可以看出,采用big endian方式存储数据是符合我们人类的思维习惯的.

为什么要注意字节序的问题呢?如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在.但是,假设你的机器是Big Endian方式在内存中存储了0x12345678,你要将该数据传给某台是Little Endian方式的机器,则对方会将你的数据翻译为0x87654321.所以,当两台采用不同字节序的主机通信时,我们就需要进行转换.

转换的关键是我们必须保证发送端和接收端的字节序是一致的,现在我们都知道该怎么做,在发送端要将本地字节序转换为网络字节序,在接收端要将网络字节序转换为本地字节序.网络字节序其实就是big endian方式.

针对这种典型情况,对一个多字节字符(ANSI中文字符或Unicode字符均可),这里举例汉字"好",其Unicode编码为597D,则Big Endian在内存低位保存59,在内存高位保存7D,Little Endian则在内存低位保存7D,在内存高位保存59.

下面我们重点介绍第二种情况,即多字节字符在文件中的保存情况.

在Windows平台下,内置的记事本文件保存时,"编码"的下拉条有四个选项:ANSI,Unicode,Unicode big endian 和 UTF-8.

1)ANSI是默认的编码方式,对于英文文件是ASCII编码,对于简体中文文件是GB2312编码,,该选项用big endian格式.

2)Unicode编码指的是UCS-2编码方式,即直接用两个字节存入字符的Unicode码.这个选项用的little endian格式.

3)Unicode big endian编码与上一个选项相对应.该选项用的big endian格式.

4)UTF-8编码,该选项用big endian格式.

均保存"好人"二字,4个文件的16进制用UltraEdit显示分别为:1) BA C3 C8 CB;    2) FF FE 7D 59 BA 4E;    3) FE FF 59 7D 4E BA;    4) EF BB BF E5 A5 BD E4 BA BA.

"好人"的ANSI编码为BA C3 C8 CB,Unicode编码为59 7D 4E BA,UTF-8编码为E5 A5 BD E4 BA BA.UTF-8编码可参考上一章实现.这里的"大端""小端"主要代表了该字符的编码在计算机里保存的顺序.但该字符被读入内存时,同样会有第一种所谓经典的"大端""小端"问题.

那就也需要网络字节序转换了?不着急,Unicode规范中推荐了一种新的标记字节顺序的方法是BOM(Byte order Mark).下面就介绍下这种方法,同时会说到这里的FF FE,  FE FF, EF BB BF是什么意思.

在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF.而FFFE在UCS中是不存在的字符.UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE".这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的.因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM.UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式.字符"FEFF"的UTF-8编码是EF BB BF.所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了.

上面这方法也仅是推荐,至于把BOM写入文件中,那更是Windows自作主张的事了.linux的文件默认编码是UTF-8,它就不会写入BOM到文件中.我想主要是WinXP支持ANSI/Unicode,可支持的编码格式过多,将文件的编码信息直接写在文件中更方便吧.

讲到这里,我们再顺便说说一个很闻名的希奇现象:当你在 windows 的记事本里新建一个文件,输入"联通"两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码.什么原因?

而当你新建一个文本文件时,记事本的编码默认是ANSI, 假如你在ANSI的编码输入汉字,那么他实际就是GB系列的编码方式,"联通"的内码是:
c1 1100 0001
aa 1010 1010
cd 1100 1101
a8 1010 1000
注意到了吗?第一二个字节,第三四个字节的起始部分的都是"110"和"10",正好与UTF8规则里的两字节模板是一致的,于是再次打开记事本时,记事本就误认为这是一个UTF8编码的文件,但我们用UTF8解码之后,什么字符都不是,所以显示乱码.而假如你在"联通"之后多输入几个字,其他的字的编码不见得又恰好是110和10开始的字节,这样再次打开时,记事本就不会坚持这是一个UTF8编码的文件,而会用ANSI的方式解读,这时乱码又不出现了.

从这也可以看出,虽然windows支持多种编码解码方式,文件中不写入BOM一般情况下同样可以被正确读出.一如linux,它也可能支持多种编解码方式,那操作系统是如何正确的做到文件的正确解码呢?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章