天天看點

位元組序

一、位元組序定義

位元組序,顧名思義位元組的順序,再多說兩句就是大于一個位元組類型的資料在記憶體中的存放順序(一個位元組的資料當然就無需談順序的問題了)。

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

在所有的介紹位元組序的文章中都會提到位元組序分為兩類: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

0x4003     0x78

0x4002     0x56

0x4001     0x34

0x4000     0x12

******************************************************************************************************************************************

為現行的計算機都是以八位一個位元組為存儲機關,那麼一個16位的整數,也就是c語言中的short,在記憶體中可能有兩種存儲順序big-endian和 litte-endian.考慮一個short整數0x3132(0x32是低位,0x31是高位),把它指派給一個short變量,那麼它在記憶體中的存儲可能有如下兩種情況

    今天碰一個關于位元組順序的問題,雖然看起來很簡單,但一直都沒怎麼完全明白這個東西,索性就找了下資料,把它弄清楚.

    因為現行的計算機都是以八位一個位元組為存儲機關,那麼一個16位的整數,也就是c語言中的short,在記憶體中可能有兩種存儲順序big-endian和litte-endian.考慮一個short整數0x3132(0x32是低位,0x31是高位),把它指派給一個short變量,那麼它在記憶體中的存儲可能有如下兩種情況:

大端位元組(big-endian):

----------------->>>>>>>>記憶體位址增大方向

short變量位址

      0x1000                 0x1001

_____________________________

|                          |

|        0x31            |       0x32

|________________ | ________________

高位位元組在低位位元組的前面,也就是高位在記憶體位址低的一端.可以這樣記住(大端->高位->在前->正常的邏輯順序)

小端位元組(little-endian):

|        0x32            |       0x31

低位位元組在高位位元組的前面,也就是低位在記憶體位址低的一端.可以這樣記住(小端->低位->在前->與正常邏輯順序相反)

可以做個實驗

在windows上下如下程式

#include <stdio.h>

#include <assert.h>

void main( void )

{

        short test;

        file* fp;

        test = 0x3132;  //(31asiic碼的’1’,32asiic碼的’2’)

        if ((fp = fopen("c:\\test.txt", "wb")) == null)

             assert(0);

        fwrite(&test, sizeof(short), 1, fp);

        fclose(fp);

}

    然後在c盤下打開test.txt檔案,可以看見内容是21,而test等于0x3132,可以明顯的看出來x86的位元組順序是低位在前.如果我們把這段同樣的代碼放到(big-endian)的機器上執行,那麼打出來的檔案就是12.這在本機中使用是沒有問題的.但當你把這個檔案從一個big-endian機器複制到一個little-endian機器上時就出現問題了.

    如上述例子,我們在big-endian的機器上建立了這個test檔案,把其複制到little-endian的機器上再用fread讀到一個short裡面,我們得到的就不再是0x3132而是0x3231了,這樣讀到的資料就是錯誤的,是以在兩個位元組順序不一樣的機器上傳輸資料時需要特别小心位元組順序,了解了位元組順序在可以幫助我們寫出移植行更高的代碼.

正因為有位元組順序的差别,是以在網絡傳輸的時候定義了所有位元組順序相關的資料都使用big-endian,bsd的代碼中定義了四個宏來處理:

#define ntohs(n)     //網絡位元組順序到主機位元組順序 n代表net, h代表host, s代表short

#define htons(n)     //主機位元組順序到網絡位元組順序 n代表net, h代表host, s代表short

#define ntohl(n)      //網絡位元組順序到主機位元組順序 n代表net, h代表host, s代表long

#define htonl(n)      //主機位元組順序到網絡位元組順序 n代表net, h代表host, s代表long

舉例說明下這其中一個宏的實作:

 #define sw16(x) \

    ((short)( \

        (((short)(x) & (short)0x00ffu)<< 8) | \

        (((short)(x) & (short)0xff00u)>> 8) ))

這裡實作的是一個交換兩個位元組順序.其他幾個宏類似.

我們改寫一下上面的程式

#define sw16(x) \

// 因為x86下面是低位在前,需要交換一下變成網絡位元組順序

#define htons(x) sw16(x)

        test = htons(0x3132); //(31asiic碼的’1’,32asiic碼的’2’)

    如果在高位元組在前的機器上,由于與網絡位元組順序一緻,是以我們什麼都不幹就可以了,隻需要把#definehtons(x) sw16(x)宏替換為 #define htons(x) (x).

    一開始我在了解這個問題時,總在想為什麼其他資料不用交換位元組順序?比如說我們write一塊buffer到檔案,最後終于想明白了,因為都是unsignedchar類型一個位元組一個位元組的寫進去,這個順序是固定的,不存在位元組順序的問題,夠笨啊..

1位元組序

由于不同的計算機系統采用不同的位元組序存儲資料,同樣一個4位元組的32位整數,在記憶體中存儲的方式就不同. 位元組序分為小尾位元組序(little endian)和大尾位元組序(big endian), intel處理器大多數使用小尾位元組序, motorola處理器大多數使用大尾(big endian)位元組序;

小尾就是低位位元組排放在記憶體的低端,高位位元組排放在記憶體的高端。例如一個4位元組的值為0x1234567的整數與高低位元組對應關系:

01 23 45 67

byte3 byte2 byte1 byte0

高位位元組--à---------à--------------à低位位元組

将在記憶體中按照如下順序排放:

記憶體位址序号 位元組在記憶體中的位址 16進制值

0x03 byte3 01

0x02 byte2 23

0x01 byte1 45

0x00 byte0 67

大尾就是高位位元組排放在記憶體的低端,低位位元組排放在記憶體的高端。例如一個4位元組的值為0x1234567的整數與高低位元組對應關系:

0x03 byte0 67

0x02 byte1 45

0x01 byte2 23

0x00 byte3 01

2       網絡位元組序

tcp/ip各層協定将位元組序定義為大尾,是以tcp/ip協定中使用的位元組序通常稱之為網絡位元組序。

3       字串在記憶體中的存儲(intel系列)

    字串和整數是相反的,是安字串的索引從低到高存儲到記憶體中的;

     char s[4] =“abc”;  

a b c \0

s[0] s[1] s[2] s[3]

記憶體位址序号 16進制值 指針p的位置

0xbffeadf7 \0 p+3

0xbffeadf6 c p+2

0xbffeadf5 b p+1

0xbffeadf4 a p

int main(void)

     char s[4] ="abc";

     char *p = s;

    printf("%02x, %02x,   %02x,    %02x\n", &s[0],&s[1], &s[2], &s[3]);

    printf("%02x, %02x,   %02x,    %02x\n", p, p+1,p+2, p+3);

    printf("%c,   %c, %c,%c\n", s[0], s[1], s[2], s[3]);

     return 0;

輸出結果:

[netcool@hfinmsp2 demo]$ ./demo001

bffeadf4,      bffeadf5,       bffeadf6,       bffeadf7

a,      b,      c,

4       整數數組在記憶體中的存儲(intel系列)

同字串一樣,但是數組裡的每一個整數的存儲是按照小尾位元組序;

5 linux系統中的處理方法

網絡位元組序作為一個标準位元組序,如果系統并沒有提供相關的轉換函數,我們可以通過以下4個宏實作本地位元組序和網絡位元組序的互相轉換:

htons():将16位無符号整數從本地位元組序轉換成網絡位元組序

htonl():将32位無符号整數從本地位元組序轉換成網絡位元組序

ntohs():将16位無符号整數從網絡位元組序轉換成本地位元組序

ntohl():将32位無符号整數從網絡位元組序轉換成本地位元組序

繼續閱讀