字節存儲排序:大端和小端的判別及轉換

當前的存儲器,多以byte爲訪問的最小單元,當一個邏輯上的地址必須分割爲物理上的若干單元時就存在了先放誰後放誰的問題,於是端(endian)的問題應運而生了,對於不同的存儲方法,就有大端(big-endian)和小端(little- endian)兩個描述。

字節排序按分爲大端和小端,概念如下

大端(big endian):低地址存放高有效字節

小端(little endian):低字節存放地有效字節

現在主流的CPU,intel系列的是採用的little endian的格式存放數據,而motorola系列的CPU採用的是big endian,ARM則同時支持 big和little,網絡編程中,TCP/IP統一採用大端方式傳送數據,所以有時我們也會把大端方式稱之爲網絡字節序

特別需要注意的是,C/C++語言編寫的程序裏數據存儲順序是跟編譯平臺所在的CPU相關的,而 JAVA編寫的程序則唯一採用big endian方式來存儲數據。這裏我就只討論C/C++語言的情況。

1.大端和小端的方式及判斷

舉個例子說明,我的機子是32位windows的系統,處理器是AMD的。對於一個int型數0x12345678,爲方便說明,這裏採用16進製表示。這個數在不同字節順序存儲的CPU中儲存順序如下:

0x12345678   16進制,兩個數就是一字節

高有效字節——>低有效字節: 12 34 56 78

          低地址位     高低址位

大端:  12  34        56   78

小端: 78  56        34   12

下面驗證下本機CPU屬於哪種字節存儲順序。代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
 
using namespace std;
 
typedef unsigned int UINT;
typedef unsigned char UCHAR;
 
int main()
{
    UINT i=0x12345678;
    cout<<hex<<i<<endl;
    UCHAR *p = (UCHAR*)&i;          //將i的地址傳給數組指針p,實際上p指向的地址是i在內存中存儲的第一個字節,大端就是0x12,小端就是0x78
    if((*p==0x78)&(*(p+1)==0x56))       
        cout<<"小端"<<endl;
    else if((*p==0x12)&(*(p+1)==0x34))
        cout<<"大端"<<endl;
    else
        cout<<"這是神馬字節順序呢?";
    return 0;
}

調試顯示時小端,我用的機子字節存儲爲小端方式。

2.大端和小端的字節轉換

當兩臺採用不同字節序的主機通信時,在發送數據之前都必須經過字節序的轉換成爲網絡字節序(即大端方式)後再進行傳輸。此外用C/C++在小端方式的機器上編寫的程序與java程序互通時也要進行大端和小端的轉換。

這裏所謂轉換就是改變字節的排序,使交互時數據保持一致。舉一個例子,還是16進製表示的數0x12345678,在小端機器上排序爲0x78563412,當內存中這樣的數傳輸時,在大端方式下就是0x78563412這個值,與原值不同,要想與原值相同,在傳輸前,在大端方式下就該是0x12345678,這時原數在內存中爲0x12345678,即將原數據0x12345678在內存存儲序列爲0x12345678,也就是要轉換成大端方式。

要傳輸值:12 34 56 78

不轉換時,小端:78 56 34 12

轉換爲大端:12 34 56 78

根據上面的大端和小端字節排序,可以方便的用移位運算完成轉換功能。從小端轉到大端代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
 
using namespace std;
 
typedef unsigned int UINT;
typedef unsigned char UCHAR;
 
int main()
{
    UINT i=0x12345678;
    cout<<hex<<i<<endl;
    UCHAR *p = (UCHAR*)&i;
    UINT num,num1,num2,num3,num4;
    num1=(UINT)(*p)<<24;
    num2=((UINT)*(p+1))<<16;
    num3=((UINT)*(p+2))<<8;
    num4=((UINT)*(p+3));
    num=num1+num2+num3+num4;
 
    cout<<"num1:"<<hex<<num1<<endl;     //看num1的16進製表示,下同
    cout<<"num2:"<<hex<<num2<<endl;
    cout<<"num3:"<<hex<<num3<<endl;
    cout<<"num4:"<<hex<<num4<<endl;
    cout<<"num:"<<hex<<num<<endl;
 
    unsigned char *q = (unsigned char*)&num;
    if((*q==0x78)&(*(q+1)==0x56))         
        cout<<"小端"<<endl;
    else if((*q==0x12)&(*(q+1)==0x34))
        cout<<"大端"<<endl;
    else
        cout<<"這是神馬字節順序呢?";
    return 0;
}

至於說(UINT)(*p)爲什麼要移24位,其實是很好理解的,將0x00000012變成0x12000000,不就是向左移24位嗎。

當然,向上面這樣寫時爲了方便理解,可以更簡單的寫一個函數用於完成上面的轉換功能,函數如下:

1
2
3
4
5
UINT EndianConvertLToB(UINT InputNum) {
    UCHAR *p = (UCHAR*)&InputNum;
    return(((UINT)*p<<24)+((UINT)*(p+1)<<16)+
               ((UINT)*(p+2)<<8)+(UINT)*(p+3));
}

同樣的原理適用於大端轉小端,但是大端轉小端時移位有差別,函數如下:

1
2
3
4
5
UINT EndianConvertBToL(UINT InputNum) {
    UCHAR *p = (UCHAR*)&InputNum;
    return(((UINT)*p)+((UINT)*(p+1)<<8)+
               ((UINT)*(p+2)<<16)+(UINT)*(p+3)<<24);
}
發佈了25 篇原創文章 · 獲贊 4 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章