字符編碼方案的演變與字節序
一、字符編碼方案的演變
1.
前文已經提及,編號字符集CCS(簡稱字符集)與字符編碼方式CEF(簡稱編碼方式)這兩個概念,在早期并沒有必要嚴格區分。
在Unicode編碼方案出現之前,字符集及其具體的編碼方式是綁定耦合在一起的,因此,“字符集”、“編碼”或“編碼方式”甚至“編碼方案”這幾個概念經常相互指代、彼此混用。
比如,字符集里的字符編號(即碼點編號)在很多文章里也稱之為字符編碼、字符碼、碼點、碼位、碼點值、碼值等,字符編碼也稱之為編碼實現、編碼方案、編碼方式、編碼格式、編碼形式、內碼、編碼值、碼值(你沒看錯,字符編號與字符編碼都有可能被簡稱為“碼值”,頭大了吧),等等,非常混亂。
2.
對于ASCII、GB2312、GBK、GB18030、Big5之類采用傳統字符編碼模型的歷史遺留方案來說,由于基本上一個字符集只使用一種編碼方式,因此這種混用問題還不大。
但在Unicode這樣采用現代字符編碼模型的全新方案出現之后,很多人受上述這些歷史遺留方案的影響,從而導致無法正確地理解字符集和編碼方式的關系,這導致了概念混亂,引起了大量誤解。
然而,對于采用現代字符編碼模型的Unicode標準來說,字符集和編碼方式是必須明確區分的。從軟件工程的角度來講,傳統字符編碼模型中緊密綁定耦合在一起的字符集及編碼方式這兩個概念,在現代字符編碼模型中被分離解耦了,而這種解耦帶來了極大的靈活性。
這意味著,對于采用現代字符編碼模型的同一個字符集,可以采用多個不同的編碼方式對其字符編號進行編碼。也因此,作為同一個Unicode字符集,目前就定義了UTF-8、UTF-16和UTF-32等(UTF,即Unicode/UCS Transformation Format)多種可選的編碼方式。
3.
所以,用Unicode來稱呼一個編碼方式已不合適,并且容易產生誤導,引發混亂和導致困惑,而應該用UTF-8、UTF-16和UTF-32來稱呼編碼方式,以Unicode來稱呼字符集,將包括Unicode字符集及各UTF編碼方式等在內的整體稱之為字符編碼方案或字符編碼系統或字符編碼標準。
另外,同一字符編碼方式CEF的碼元序列,在計算機實際處理、存儲和傳輸時,還需再次編碼轉換為字符編碼模式CES的字節序列。
字符編碼方式CEF的碼元序列可理解為字符編碼的邏輯表示形式,相對而言,字符編碼模式CES的字節序列則可理解為字符編碼在計算機中的物理表示形式。
而字節序列,則涉及到了不同的字節序(Byte-Order,主要分為大端序Big-Endian、小端序Little-Endian)。
二、字節序
1.
字節序,又稱字節順序,其英文為Byte-Order;另外英文中還可稱為Endianness,因此也翻譯為端序。
Endianness這個詞,源自于1726年Jonathan Swift的名著:Gulliver's Travels(格列佛游記)。在書中有一個童話故事,大意是指Lilliput小人國的國王下了一道指令,規定其人民在剝水煮蛋時必須從little-end(小端)開始剝。這個規定惹惱了一群覺得應該要從big-end(大端)開始剝的人。事情發展到后來演變成了一場戰爭。后來,支持小端的人被稱為little-endian,反之則被稱為big-endian(在英語中后綴“-ian”表示“xx人”之意)。
1980年,Danny Cohen在他的論文“On Holy Wars and a Plea for Peace”中,第一次使用了Big-endian和Little-endian這兩個術語,最終它們成為了異構計算機系統之間進行通訊、交換數據時所要考慮的極其重要的一個問題。
(注:所謂異構是指不同架構、不同結構、不同構造等,而這里的異構計算機系統,主要指的是采用不同CPU和/或不同操作系統的計算機系統。)
2.
為什么會存在字節序的問題?
當然不會是像童話故事里那樣出于“無厘頭”的原因,而是因為歷史上設計不同計算機系統的人在當時基于各自的理由和原因(這里的理由和原因網上存在著各種不同的說法,但也或許根本就沒有具體理由和原因,只是設計人員的個人偏好,甚至是隨意決定的),在各自計算機系統的設計上作出了不同的選擇。
字節序共分為三種:
大端序BE(Big-Endian,也稱高尾端序);
小端序LE(Little-Endian,也稱為低尾端序);
中間序ME(Middle-Endian,也稱為混合序),不太常用,本文不作介紹。
3.
字節序,具體來說,就是多字節數據(大于一個字節的數據)在計算機中存儲、讀取時其各個字節的排列順序。
字節序也被稱為端序,這里的“端”,是指多字節數據中位于兩端的字節,很多情況下還特指尾端字節(也稱為小端字節)。
所謂尾端字節或小端字節,假設按照人對文字通常從左到右(或從上到下)的讀寫順序來看的話,多字節數據位于右端(或下端)的低位字節就是尾端字節或小端字節,而將位于左端(或上端)的高位字節稱為頭端字節或大端字節。
當然,如果不按照通常從左到右的順序,而是按照從右到左的順序,那么多字節數據位于右端的高位字節就是頭端字節或大端字節,而將位于左端的低位字節稱為尾端字節或小端字節。
可見,不論讀寫順序如何,所謂大端、頭端,指的是多字節數據中,代表更大數值的那個字節所在的那一端,而相反的那一端則是小端、尾端。
4.
而要徹底講清楚大端序(高尾端序)、小端序(低尾端序),則需要從人讀寫二進制數的方向和內存地址的增長方向兩者相結合講起:
人讀寫二進制數的方向為(這是確定不變的):左--->右,大端/頭端/高位--->小端/尾端/低位;或上--->下,大端/頭端/高位--->小端/尾端/低位;
內存地址的增長方向則為(這是確定不變的):左--->右,低地址--->高地址;或上--->下,低地址--->高地址。
不過,計算機在內存中存取數據的方向則不是確定不變的,而是分為兩種(注意,由于人的讀寫方向和內存地址增長方向是確定不變的,因此這里指的是計算機在內存中“書寫”或”閱讀“數據的方向):
1)?左--->右,大端/頭端/高位--->小端/尾端/低位;或上--->下,大端/頭端/高位--->小端/尾端/低位;
這種情況下,站在人的讀寫方向和內存地址增長方向(這兩者的方向剛好一致)的角度來看,則是:大端在左(或在上),所以稱之為大端序;或者說尾端在內存高地址,所以稱之為高尾端序(即內存高地址存放多字節數據的尾端字節的字節順序)。
2)?右--->左,大端/頭端/高位--->小端/尾端/低位;或下--->上,大端/頭端/高位--->小端/尾端/低位。
這種情況下,站在人的讀寫方向和內存地址增長方向(這兩者的方向剛好一致)的角度來看,則是:小端在左(或在上),所以稱之為小端序;或者說尾端在內存低地址,所以稱之為低尾端序(即內存低地址存放多字節數據的尾端字節的字節順序)。
【特別提示:大端序、小端序特別容易搞混,不好記憶;因此,建議使用高尾端序、低尾端序,本人是按下面這個方法來記憶的,很容易記住:存儲的數據分頭和尾——左頭右尾,內存的地址分低和高——左低右高;因此,“高尾端”表示內存的高地址存儲數據的尾端,“低尾端”表示內存的低地址存儲數據的尾端。】
5.
注意,這里的“數據”指的是數據類型意義上的數據,因此,準確地說字節序只跟多字節的整型數據類型有關,比如int、short、long型;跟單字節的整型數據類型byte無關。
那么,為什么就只跟多字節的整型數據有關,而跟單字節的整型數據無關呢?
因為在現代計算機中,字節是計算機數據存儲的基本單位,對于整體上的單一字節(a byte),涉及到的是其8個比特位的順序(位序、比特序,由于一般直接由硬件處理,程序員大致了解即可,這里不深入探討),顯然不存在字節序問題。
然而,對于在數據類型上作為一個整體的多字節數據而言,它是由各個可被單獨尋址的字節所組成的(處理器尋址的最小單位一般是1個字節),由于歷史的原因,其各個字節的存儲順序在不同的系統平臺(包括CPU和操作系統)上是不同的。
6.
也就是說,如果計算機處理的數據是單字節數據類型(byte),是不存在字節序問題的,因為單個字節已經是處理器尋址的最小單位以及存儲和傳輸的最小單元了,存儲時單字節數據類型直接進行,讀取時也不存在根據前后2個字節才能解析出其值的情況,而構造字節流時也不會從一個單字節數據類型的值當中產生2個或以上的字節(既然是單字節數據類型,構造字節流時當然只可能產生1個字節)。
但是,如果計算機處理的數據是多字節數據類型(int、short、long等),雖然由于構成它們的2個或2個以上的字節是作為一個整體來進行處理的(比如以匯編語言中的數據類型word或dword為單位進行一次性處理,而不是以byte為單位分次處理;更深入地來講,CPU一般是以字為一個整體來處理數據的,當單個數據不足一個字長時,則將多個數據“拼成”一個字再進行處理),但問題是字節才是CPU對內存尋址的最小單位以及存儲和傳輸的最小單元。
因此,CPU在讀取作為一個整體來進行處理的多字節數據類型的數據時,必須根據前后2個或2個以上的字節來解析出一個多字節數據類型的值;而且構造字節序列時也會從一個多字節數據類型的值當中產生2個或2個以上的字節。
7.
這樣一來,多字節數據類型的數據內部各字節間的排列順序,是會影響從字節序列恢復到數值的;反之,也會影響從數值到字節序列的構造。
所以,在存儲和讀取多字節數據類型的數據時,必須按照計算機系統所規定的字節序進行(這一點程序員了解即可,計算機會自動處理);而尤其是在跨字節序不同的異構計算機系統進行通訊并交換數據時,通訊的任何一方更是必須明確對方所采用的字節序,然后雙方將各自接收到的數據按各自的字節序對數據進行轉換(有時候需要程序員專門編寫轉換程序),否則通訊將會出錯,甚至直接失敗。
8.
實際上,int、short、long等數據類型一般是編程語言層面的概念,更進一步而言,這其實涉及到了機器硬件層面(即匯編語言)中的數據類型byte字節、word字、dword雙字等在硬件中的表達與處理機制(實質上字節序跟CPU寄存器的位數、存放順序密切相關)。具體可參看附文:《本質啊本質之一:數據類型的本質》、《寄存器與字、字節》。
【附:本質啊本質之一:數據類型的本質
CSDN博客 博主:band_of_brothers?發表于:2007-10-10 22:20
研究一個層面的問題,往往要從更深的層面找尋答案。這就如C語言與匯編、匯編與機器指令,然而終究要有個底限,這個底限以能使我們心安理得為準,就好比公理之于數學、三大定律之于宏觀物理。
在這里就將機器指令作為最后的底限吧,盡管再深入下去還有微指令,但那畢竟是太機器了,可以了。以下所有從C代碼編譯生成匯編代碼用的是命令:cl xxx..c /Fa /Ze。
類型的本質
類型這個概念,好多地方都有講,但說實話,你真的理解嗎?什么是類型?類型是一個抽象的概念還是一個真實的存在?嗯?
開始:
1)“好多相同或相似事物的綜合”(辭海)。
2)X86機器的數據類型有byte、word、dword、fword、tword、qword,等等。
3)“給內存塊一個明確的名字,就象郵件上的收件人一樣。給其一個明確的數據類型,就好象說,郵件是一封信,還是一個包裹。”
4)類型就是一次可以操作的塊的大小,就是一個單位,就像克、千克、噸一樣。雙字一次操作32位;字,一次操作16位;如果沒有各種類型,機器只有一個類型單位——字節,那么當需要一個4字節大小的塊時,就需要4次操作,而如果有雙字這個類型單位,那么只需要一次操作就可以了。
5)類型,是機器層面支持的,不是軟的,是硬的,有實實在在的機器碼為證。
類型的反匯編
W32dasm反匯編出來的東西,可以看出不同的類型,機器碼不同,說明類型是機器硬件級別支持的,不是通過軟件實現的,更不是一個抽象的概念。
Opcodes上關于mov的機器碼講的更清楚:
需要說明的是,一些大的類型單位,如qword等,在mov等標準指令里是沒有的,在一些特殊指令里才能用到,如浮點指令:fmul qword ptr [0067FB08] 機器碼:DC0D08FB6700。】
【附:寄存器與字、字節
字節:記為byte,一個字節由8個比特(bit)組成,可以直接存在一個8位寄存器里
1 0 1 0 1 0 0 1
? ? 一個字節
字:記為word,一個字由2個字節(共16比特)組成,可以直接存在一個16位寄存器里
?1 1 1 1 1 1 1 1? 0 0 0 0 0 0 0 0
??????高位字節? ? ? ?? 低位字節
一個8位寄存器用2位十六進制數表示,只能存1個字節(1個byte類型的數據)
一個16位寄存器用4位十六進制數表示,可存1個字(1個word類型的數據)或2個字節(2個byte類型的數據)
一個32位寄存器用8位十六進制數表示,可存2個字(1個dword類型的數據或2個word類型的數據)或4個字節(4個byte類型的數據)】
(笨笨阿林原創文章,轉載請注明出處)
9.
下面簡要介紹一下字節序的三種類型:
a)?小端序Little-Endian(低尾端序)
就是低位字節(即小端字節)存放在內存的低地址,而高位字節(即大端字節)存放在內存的高地址。
這是最符合人的直覺思維的字節序(但卻不符合人的讀寫習慣),因為從人的第一觀感來說,低位字節的值小,對應放在內存地址也小的地方,也即內存中的低位地址;反之,高位字節的值大,對應放在內存地址大的地方,也即內存中的高位地址。
b)?大端序Big-Endian(高尾端序)
就是高位字節(即大端字節)存放在內存的低地址,低位字節(即小端字節)存放在內存的高地址。
這是最符合人平時的讀寫習慣的字節序(但卻不符合人的直覺思維),因為不用像在Little-EndIan中還需考慮字節的高位、低位與內存的高地址、低地址的對應關系,只需把數值按照人通常的書寫習慣,從高位到低位的順序直接在內存中從左到右或從上到下(下圖中就是從上到下)按照由低到高的內存地址,一個字節一個字節地填充進去。
?c)?中間序Middle-Endian(混合序Mixed-Endian)
混合序具有更復雜的順序。以PDP-11為例,32位的0x0A0B0C0D被存儲為:
混合序較少見,常見的多為大端序和小端序。
(注:其實還一種網絡字節序(network byte order網絡字節順序、網絡序),這里不做深入介紹。)
10.
Intel和AMD的X86平臺,以及DEC(Digital Equipment Corporation數字設備公司,后與Compaq合并,之后Compaq又與HP合并)采用的是Little-Endian,而像IBM、Sun的SPARC采用的就是Big-Endian。有的嵌入式平臺是Big-Endian的。JAVA字節序也是Big-Endian的。
當然,這不代表所有情況。有的CPU既能工作于小端序,又能工作于大端序,比如ARM、Alpha、摩托羅拉的Power PC、SPARC V9、MIPS、PA-RISC和IA64等體系結構(具體情形可參考處理器手冊),其字節序是可切換的,這種可切換的特性可以提高效率或者簡化網絡設備和軟件的邏輯。
這種可切換的字節序被稱為Bi-Endian(前綴“Bi-”表示“雙邊的、二重的、兩個的”),用于硬件上意指計算機存儲時具有可以使用兩種不同字節序中任意一種的能力。具體這類CPU是大端序還是小端序,和具體設置有關。如Power PC可支持Little-Endian字節序,但其默認配置為Big-Endian字節序。
11.
一般來說,大部分用戶的操作系統,如Windows、FreeBsd、Linux是Little-Endian的;少部分,如Mac OS是Big-Endian的。
具體參見下表:
12.
所以說,Little Endian還是Big Endian與操作系統和CPU類型都有關系。因此在一個計算機系統中,有可能同時存在大端序和小端序兩種字節序的現象。
這一現象為系統的軟硬件設計帶來了不小的麻煩,這要求系統設計工程師必須深入理解大端序和小端序的差別。大端序與小端序的差別體現在一個處理器的寄存器、指令集、系統總線等各個層次中。
其實,很多技術人員在實際的非跨平臺桌面應用開發過程中都很少會直接和字節序打交道,但在跨平臺及網絡應用開發過程中因為涉及到異構計算機系統間的通訊交流,字節序是很難回避的問題。
(笨笨阿林原創文章,轉載請注明出處)