1. 程式人生 > >【字元編碼系列之一】多位元組資料的位元組序

【字元編碼系列之一】多位元組資料的位元組序

字元和位元組的區別

  • 位元組(byte):是一個8bit的儲存單元,取值範圍是0x00~0xFF。
  • 字元(character):為語言意義上的一個符號,範圍不一定。一個字元佔用的位元組數,隨著編碼方式的不同而不同。可能是一個位元組,也可能是多個。對於大於一個位元組的字元,我們稱之為多位元組資料。

大端對齊 (Big Endian) 和小端對齊 (Little Endian) 

一個字元可能佔用多個位元組,那麼這多個位元組在計算機中如何儲存呢?
比如字元0xABCD:

//小端對齊(Little Endian)
addr:0x2c2d98	CD
addr:0x2c2d99	AB

//大端對齊(Big Endian)
addr:0x2c2d98	AB
addr:0x2c2d99	CD
大端對齊(Big Endian):低地址存放 多位元組資料 的高位元組。
小端對齊(Little Endian):低地址存放 多位元組資料 的低位元組。

這就是位元組序的問題。為什麼要注意位元組序的問題呢?當然,如果你寫的程式只在單機環境下面執行,不和別人的程式打交道,那麼你完全可以忽略位元組序的存在。但是,如果你的程式要跟別人的程式產生互動。那麼對於以下資料:

addr:0x2c2d98	AB
addr:0x2c2d99	CD
小端對齊方式讀出的就是0xCDAB,而大端對齊方式上讀出的就是0xABCD.這就是對於同一結構的不同解釋導致的通訊誤解。

注意,這裡所說的大端對齊與小端對齊,指的是對於一個多位元組資料內部,而不是不同的資料之間 怎麼儲存。

舉個例子,我們假定int型別佔用4個位元組

int a = 0x01020304, b = 0x05060708;

那麼不管是大端對齊還是小端對齊,是指的變數a內部是怎麼表示的,而不是指變數a與變數b之間的先後關係。

//小端對齊(Little Endian)
//變數a
addr:0x2c2d92	04
addr:0x2c2d93	03
addr:0x2c2d94	02
addr:0x2c2d95	01

//變數b
addr:0x2c2d96	08
addr:0x2c2d97	07
addr:0x2c2d98	06
addr:0x2c2d99	05

//大端對齊(Big Endian)
//變數a
addr:0x2c2d92	01
addr:0x2c2d93	02
addr:0x2c2d94	03
addr:0x2c2d95	04

//變數b
addr:0x2c2d96	05
addr:0x2c2d97	06
addr:0x2c2d98	07
addr:0x2c2d99	08

我們看到,大小端對齊,指的是變數a的內部怎麼儲存,而不是指變數a與變數b之間是怎麼儲存的。

下面這張圖生動的闡釋了剛講過的內容。


所有網路協議也都是採用大端對齊(Big Endian)的方式來傳輸資料的。所以有時我們也會把Big Endian方式稱之為網路位元組序。當兩臺採用不同位元組序的主機通訊時,在傳送資料之前都必須經過位元組序的轉換成為網路位元組序後再進行傳輸。 


判斷一臺機器是大端對齊還是小端對齊,可以通過下面這個小程式:

long iNum = 1;	//long型別肯定至少是2個位元組
if(1 == *(char *)(&iNum))   //取iNum地址並強制轉換為char*型別再取值,此時取到的值是低位地址上的值 
    std::cout << "Little Endian" << std::endl; 
else 
    std::cout << "Big Endian" << std::endl; 

BOM

在Unicode編碼中有一個叫做 "Zero Width No-Break Space" ,中文譯名作“零寬無間斷間隔”的字元,它的編碼是U+FEFF,也就是十六進位制的0xFEFF。字元U+FEFF如果出現在位元組流的開頭,則用來標識該位元組流的位元組序,用來表示到底是高位在前還是低位在前。如果它出現在位元組流的中間,則表達零寬度非換行空格的意義,使用者看起來就是一個空格。從Unicode3.2開始,U+FEFF只能出現在位元組流的開頭,只能用於標識位元組序。所以U+FEFF又有一個名字,那就是位元組順序標記(Byte Order Mark),簡稱BOM。現在U+FEFF只用作BOM,除此以外的用法已被捨棄。取而代之的是,使用U+2060來表達零寬度無斷空白。

為什麼U+FEFF可以用作BOM呢?那是因為由於Unicode中沒有定義U+FFFE,而只定義了U+FEFF,因此只要出現 FF FE 或者 FE FF 這樣的位元組序列,就可以認為它是U+FEFF,並且可以判斷出是Big Endian還是Little Endian了。

結語

  • 大端對齊(Big Endian)、小端對齊(Little Endian)是跟CPU有關的,每一種CPU只能是兩者之一。IA架構(Intel Architecture)的CPU中是小端對齊的,而PowerPC 、SPARC和Motorola處理器則是大端對齊的。這其實就是所謂的主機位元組序。
  • 網路位元組序是指資料在網路上傳輸時是大端對齊還是小端對齊的。在Internet的網路位元組序是大端對齊(Big Endian)。
  • 所謂的JAVA位元組序指的是在JAVA虛擬機器中多位元組型別資料的存放順序,JAVA位元組序也是大端對齊(Big Endian)。
  • 在用C/C++寫通訊程式時,在傳送資料前務必把多位元組資料,從主機位元組序轉換到網路位元組序,而接收資料後對多位元組資料也必須從網路位元組序轉換到主機位元組序。
  • 如果通訊的一方是JAVA程式、一方是C/C++程式時,則需要在C/C++一側使用以上幾個方法進行位元組序的轉換,而JAVA一側,則不需要做任何處理,因為JAVA位元組序與網路位元組序都是大端對齊(Big Endian),只要C/C++一側能正確進行轉換即可(傳送前從主機序到網路序,接收時反變換)。如果通訊的雙方都是JAVA,則不用考慮位元組序的問題了。