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,它也可能支持多種編解碼方式,那操作系統是如何正確的做到文件的正確解碼呢?

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