當前的存儲器,多以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屬於哪種字節存儲順序。代碼如下:
#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
根據上面的大端和小端字節排序,可以方便的用移位運算完成轉換功能。從小端轉到大端代碼如下:
#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*)# 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位嗎。
當然,向上面這樣寫時為了方便理解,可以更簡單的寫一個函數用於完成上面的轉換功能,函數如下:
UINT EndianConvertLToB(UINT InputNum) { UCHAR *p = (UCHAR*)&InputNum; return(((UINT)*p<<24)+((UINT)*(p+1)<<16)+ ((UINT)*(p+2)<<8)+(UINT)*(p+3)); }
同樣的原理適用於大端轉小端,但是大端轉小端時移位有差別,函數如下:
UINT EndianConvertBToL(UINT InputNum) { UCHAR *p = (UCHAR*)&InputNum; return(((UINT)*p)+((UINT)*(p+1)<<8)+ ((UINT)*(p+2)<<16)+(UINT)*(p+3)<<24); }