大端字節序,小端子節序

 

一、字節序定義

 

字節序,顧名思義字節的順序,再多說兩句就是大於一個字節類型的數據在內存中的存放順序(一個字節的數據當然就無需談順序的問題了)。

其實大部分人在實際的開發中都很少會直接和字節序打交道。唯有在跨平臺以及網絡程序中字節序纔是一個應該被考慮的問題。

在所有的介紹字節序的文章中都會提到字節序分爲兩類:Big-Endian和Little-Endian。引用標準的Big-Endian和Little-Endian的定義如下:
a) Little-Endian就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。
b) Big-Endian就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。
c) 網絡字節序:4個字節的32 bit值以下面的次序傳輸:首先是0~7bit,其次8~15bit,然後16~23bit,最後是24~31bit。這種傳輸次序稱作大端字節序。由於 TCP/IP首部中所有的二進制整數在網絡中傳輸時都要求以這種次序,因此它又稱作網絡字節序。比如,以太網頭部中2字節的“以太網幀類型”,表示後面數據的類型。對於ARP請求或應答的以太網幀類型來說,在網絡傳輸時,發送的順序是0x08,0x06。在內存中的映象如下圖所示:
棧底 (高地址)
---------------
0x06 -- 低位 
0x08 -- 高位
---------------
棧頂 (低地址)
該字段的值爲0x0806。按照大端方式存放在內存中。

 

二、高/低地址與高低字節

 

首先我們要知道我們C程序映像中內存的空間佈局情況:在《C專家編程》中或者《Unix環境高級編程》中有關於內存空間佈局情況的說明,大致如下圖:
----------------------- 最高內存地址 0xffffffff
 | 棧底
 .
 .              棧
 .
  棧頂
-----------------------
 |
 |
\|/

NULL (空洞)

/|\
 |
 |
-----------------------
                堆
-----------------------
未初始化的數據
----------------(統稱數據段)
初始化的數據
-----------------------
正文段(代碼段)
----------------------- 最低內存地址 0x00000000

以上圖爲例如果我們在棧上分配一個unsigned char buf[4],那麼這個數組變量在棧上是如何佈局的呢[注1]?看下圖:
棧底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
棧頂 (低地址)

現在我們弄清了高低地址,接着來弄清高/低字節,如果我們有一個32位無符號整型0x12345678(呵呵,恰好是把上面的那4個字節buf看成 一個整型),那麼高位是什麼,低位又是什麼呢?其實很簡單。在十進制中我們都說靠左邊的是高位,靠右邊的是低位,在其他進制也是如此。就拿 0x12345678來說,從高位到低位的字節依次是0x12、0x34、0x56和0x78。

高低地址和高低字節都弄清了。我們再來回顧一下Big-Endian和Little-Endian的定義,並用圖示說明兩種字節序:
以unsigned int value = 0x12345678爲例,分別看看在兩種字節序下其存儲情況,我們可以用unsigned char buf[4]來表示value:
Big-Endian: 低地址存放高位,如下圖:
棧底 (高地址)
---------------
buf[3] (0x78) -- 低位
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) -- 高位
---------------
棧頂 (低地址)

Little-Endian: 低地址存放低位,如下圖:
棧底 (高地址)
---------------
buf[3] (0x12) -- 高位
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) -- 低位
---------------
棧頂 (低地址)

在現有的平臺上Intel的X86採用的是Little-Endian,而像Sun的SPARC採用的就是Big-Endian。

三、例子

嵌入式系統開發者應該對Little-endian和Big-endian模式非常瞭解。採用Little-endian模式的CPU對操作數的存放方式是從低字節到高字節,而Big-endian模式對操作數的存放方式是從高字節到低字節。

例如,16bit寬的數0x1234在Little-endian模式CPU內存中的存放方式(假設從地址0x4000開始存放)爲:

內存地址  存放內容
 0x4001    0x12
 0x4000    0x34

而在Big-endian模式CPU內存中的存放方式則爲:

內存地址  存放內容
 0x4001    0x34
 0x4000    0x12
 
32bit寬的數0x12345678在Little-endian模式CPU內存中的存放方式(假設從地址0x4000開始存放)爲:

內存地址  存放內容
 0x4003     0x12
 0x4002     0x34
 0x4001     0x56
 0x4000     0x78
 
而在Big-endian模式CPU內存中的存放方式則爲:

內存地址  存放內容
 0x4003     0x78
 0x4002     0x56
 0x4001     0x34
 0x4000     0x12

四、大端小端檢測方法:
如何檢查處理器是big-endian還是little-endian?
聯合體union的存放順序是所有成員都從低地址開始存放,利用該特性就可以輕鬆地獲得了CPU對內存採用Little-endian還是Big-endian模式讀寫。
int checkCPUendian()
{
union
{
unsigned int a;
unsigned char b;
}c;
c.a = 1;
return (c.b == 1);
}
/*return 1 : little-endian, return 0:big-endian*/

網絡字節順序

1、字節內的比特位不受這種順序的影響
比如一個字節 1000 0000 (或表示爲十六進制 80H)不管是什麼順序其內存中的表示法都是這樣。
2、大於1個字節的數據類型纔有字節順序問題
比如 Byte A,這個變量只有一個字節的長度,所以根據上一條沒有字節順序問題。所以字節順序是“字節之間的相對順序”的意思。
3、大於1個字節的數據類型的字節順序有兩種
比如 short B,這是一個兩字節的數據類型,這時就有字節之間的相對順序問題了。
網絡字節順序是“所見即所得”的順序。而Intel類型的CPU的字節順序與此相反。
比如上面的 short B=0102H(十六進制,每兩位表示一個字節的寬度)。所見到的是“0102”,按一般數學常識,數軸從左到右的方向增加,即內存地址從左到右增加的話,在內存中這個 short B的字節順序是:
01 02
這就是網絡字節順序。所見到的順序和在內存中的順序是一致的!
而相反的字節順序就不同了,其在內存中的順序爲:02 01
假設通過抓包得到網絡數據的兩個字節流爲:01 02
如果這表示兩個 Byte類型的變量,那麼自然不需要考慮字節順序的問題。
如果這表示一個 short 變量,那麼就需要考慮字節順序問題。根據網絡字節順序“所見即所得”的規則,這個變量的值就是:0102
假設本地主機是Intel類型的,那麼要表示這個變量,有點麻煩:
定義變量 short X,
字節流地址爲:pt,按順序讀取內存是爲
x=*((short*)pt);
那麼X的內存順序當然是 01 02
按非“所見即所得”的規則,這個內存順序和看到的一樣顯然是不對的,所以要把這兩個字節的位置調換。
調換的方法可以自己定義,但用已經有的API還是更爲方便。

網絡字節順序與主機字節順序
NBO與HBO 網絡字節順序NBO(Network Byte Order):按從高到低的順序存儲,在網絡上使用統一的網絡字節順序,可以避免兼容性問題。主機字節順序(HBO,Host Byte Order):不同的機器HBO不相同,與CPU設計有關計算機數據存儲有兩種字節優先順序:高位字節優先和低位字節優先。Internet上數據以高位 字節優先順序在網絡上傳輸,所以對於在內部是以低位字節優先方式存儲數據的機器,在Internet上傳輸數據時就需要進行轉換。

htonl()
簡述:
    將主機的無符號長整形數轉換成網絡字節順序。
    #include <winsock.h>
    u_long PASCAL FAR htonl( u_long hostlong);
    hostlong:主機字節順序表達的32位數。
註釋:
    本函數將一個32位數從主機字節順序轉換成網絡字節順序。
返回值:
    htonl()返回一個網絡字節順序的值。

inet_ntoa()
簡述:
將網絡地址轉換成“.”點隔的字符串格式。
#include <winsock.h>
char FAR* PASCAL FAR inet_ntoa( struct in_addr in);
in:一個表示Internet主機地址的結構。
註釋:
本函數將一個用in參數所表示的Internet地址結構轉換成以“.” 間隔的諸如“a.b.c.d”的字符串形式。請注意inet_ntoa()返回的字符串存放在WINDOWS套接口實現所分配的內存中。應用程序不應假設 該內存是如何分配的。在同一個線程的下一個WINDOWS套接口調用前,數據將保證是有效。
返回值:
若無錯誤發生,inet_ntoa()返回一個字符指針。否則的話,返回NULL。其中的數據應在下一個WINDOWS套接口調用前複製出來。

網絡中傳輸的數據有的和本地字節存儲順序一致,而有的則截然不同,爲了數據的一致性,就要把本地的數據轉換成網絡上使用的格式,然後發送出去,接收的時候 也是一樣的,經過轉換然後纔去使用這些數據,基本的庫函數中提供了這樣的可以進行字節轉換的函數,如和htons( ) htonl( ) ntohs( ) ntohl( ),這裏n表示network,h表示host,htons( ) htonl( )用於本地字節向網絡字節轉換的場合,s表示short,即對2字節操作,l表示long即對4字節操作。同樣ntohs( )ntohl( )用於網絡字節向本地格式轉換的場合

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