Big endian and Little endian
談到字節排序的問題,必然牽涉到兩大CPU派系。那就是Motorola的PowerPC系列CPU和Intel的x86系列CPU。PowerPC系列採用big endian方式存儲數據,而x86系列則採用little endian方式存儲數據。ARM同時支持 big和little,實際應用中通常使用little endian。那麼究竟什麼是big endian,什麼又是little endian呢?
其實big endian是指低地址存放最高有效字節(MSB),而little endian則是低地址存放最低有效字節(LSB)。用文字說明可能比較抽象,下面用圖像加以說明。比如數字0x12345678在兩種不同字節序CPU中的存儲順序如下所示:
Big Endian
一個Word中的高位的Byte放在內存中這個Word區域的低地址處
低地址 高地址
----------------------------------------->符合思維習慣
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 12 | 34 | 56 | 78 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Little Endian
一個Word中的低位的Byte放在內存中這個Word區域的低地址處
低地址 高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 78 | 56 | 34 | 12 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
從上面兩圖可以看出,採用big endian方式存儲數據是符合我們人類的思維習慣的。必須注意的是:一個Word的長度是16位,一個Byte的長度是8位。如果一個數超過一個Word的長度,必須先按Word分成若干部分,然後每一部分(即每個Word內部)按Big-Endian或者Little-Endian的不同操作來處理字節。
一個例子:
如果我們將0x1234abcd寫入到以0x0000開始的內存中,則結果爲
big-endian little-endian
0x0000 0x12 0xcd
0x0001 0x34 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12
需要特別說明的是,以上假設機器是每個內存單元以8位即一個字節爲單位的.簡單的說,little endian把低位存放到高位,而big endian把低位存放到低位. 現在主流的CPU,intel系列的是採用的little endian的格式存放數據,而motorola系列的CPU採用的是big endian.
以下是判斷字節存儲順序的可移植的C語言代碼:
/*可移植的用於判斷存儲格式是
little endian還是big ednian的C代碼*/
#include < stdio.h >
union
{
long Long;
char Char[ sizeof ( long )];
} u;
int main()
{
u.Long = 1 ;
if (u.Char[ 0 ] == 1 )
{
printf( " Little Endian!/n " );
}
else if (u.Char[ sizeof ( long ) - 1 ] == 1 )
{
printf( " Big Endian!/n " );
}
else
{
printf( " Unknown Addressing!/n " );
}
printf( " Now, Let's look at every byte in the memory!/n " );
for ( int i = 0 ; i < sizeof ( long ); ++ i)
{
printf( " [%x] = %x/n " , & u.Char[i], u.Char[i]);
}
return 0 ;
}
在網絡編程中,TCP/IP統一採用big endian方式傳送數據,也就是說,假設現在是在一個字節順序是little endian的機器上傳送數據,要求傳送的數據是0XCEFABOBO,那麼你就要以0XBOBOFACE的順序在unsigned int中存放這個數據,只有這樣才能保證存放的順序滿足TCP/IP的字節順序要求.很多時候,需要自己編寫應用層的協議,字節順序的概念在這個時候就顯得及其的重要了.
下面給出的是在big endian和little endian中相互轉換的代碼,C語言強大的位操作的能力在這裏顯示了出來:
/*在little endian和big ednian之間相互轉化數據的演示代碼*/
#include < stdio.h >
#define SIZE_OF_UNSIGNEDINT sizeof (unsigned int )
#define SIZE_OF_UNSIGNEDCHAR sizeof (unsigned char )
void put_32(unsigned char * cmd, unsigned int data)
{
int i;
for (i = SIZE_OF_UNSIGNEDINT - 1 ; i >= 0 ; -- i)
{
cmd[i] = data % 256 ;
// 或者可以:
// cmd[i] = data & 0xFF;
data = data >> 8 ;
}
}
unsigned int get_32(unsigned char * cmd)
{
unsigned int ret;
int i;
for (ret = 0 , i = 0; i <= SIZE_OF_UNSIGNEDINT - 1 ; i++)
{
ret = ret << 8 ;
ret |= cmd[i];
}
return ret;
}
int main( void )
{
unsigned char cmd[SIZE_OF_UNSIGNEDINT];
unsigned int data, ret;
unsigned char * p;
int i;
data = 0x12345678 ;
printf( "data = 0x%x " , data);
// 以字節爲單位打印出數據
printf("/nthe default byte order is :/n");
p = (unsigned char * )( & data);
for(i = 0 ; i < SIZE_OF_UNSIGNEDINT; ++ i)
{
printf( "0x%x " , * p ++ );
}
printf( " /n " );
//以相反的順序存放到cmd之中
printf("/nafter convert,the byte order is :/n");
put_32(cmd, data);
p=cmd;
for (i = 0 ; i < SIZE_OF_UNSIGNEDINT; ++ i)
{
printf( "0x%x " , *p++);
}
// 再以相反的順序保存數據到ret中
// 保存之後的ret數值應該與data相同
ret = get_32(cmd);
printf( "/n/nret = 0x%x " , ret);
p = (unsigned char * )( & ret);
printf("/nafter restore,the byte order is :/n");
for(i = 0 ; i < SIZE_OF_UNSIGNEDINT; i++)
{
printf( "0x%x " , *p++);
}
printf( " /n " );
return 0 ;
}
爲什麼要注意字節序的問題呢?你可能這麼問。當然,如果你寫的程序只在單機環境下面運行,並且不和別人的程序打交道,那麼你完全可以忽略字節序的存在。但是,如果你的程序要跟別人的程序產生交互呢?在這裏我想說說兩種語言。C/C++語言編寫的程序裏數據存儲順序是跟編譯平臺所在的CPU相關的,而JAVA編寫的程序則唯一採用big endian方式來存儲數據。試想,如果你用C/C++語言在x86平臺下編寫的程序跟別人的JAVA程序互通時會產生什麼結果?就拿上面的0x12345678來說,你的程序傳遞給別人的一個數據,將指向0x12345678的指針傳給了JAVA程序,由於JAVA採取big endian方式存儲數據,很自然的它會將你的數據翻譯爲0x78563412。什麼?竟然變成另外一個數字了?是的,就是這種後果。因此,在你的C程序傳給JAVA程序之前有必要進行字節序的轉換工作。
所有網絡協議也都是採用big endian的方式來傳輸數據的。所以有時我們也會把big endian方式稱之爲網絡字節序。當兩臺採用不同字節序的主機通信時,在發送數據之前都必須經過字節序的轉換成爲網絡字節序後再進行傳輸。ANSI C中提供了下面四個轉換字節序的宏。
當前的存儲器,多以byte爲訪問的最小單元,當一個邏輯上的整理必須分割爲物理上的若干單元時就存在了先放誰後放誰的問題,於是endian的問題應運而生了,對於不同的存儲方法,就有Big-endian和Little-endian兩個描述.(這兩個術語來自於 Jonathan Swift 的《《格利佛遊記》其中交戰的兩個派別無法就應該從哪一端--小端還是大端--打開一個半熟的雞蛋達成一致。在那個時代,Swift是在諷刺英國和法國之間的持續衝突,Danny Cohen,一位網絡協議的早期開創者,第一次使用這兩個術語來指代字節順序,後來這個術語被廣泛接納了。)
存在“如果說"跟word或者說字長根本就沒關係",假設有一數據文件裏面有N多數順序排布,如果想以Little-Endian format 讀入內存某區域,那麼應該怎麼讀?怎麼排?”這樣的問題是由於對於endian的實質理解的偏差,endian指的是當物理上的最小單元比邏輯上的最小單元小時,邏輯到物理的單元排布關係。這裏的“有一數據文件裏面有N多數順序排布”,這個“有一數據”顯然不是邏輯上的最小單元,而其中的“N多數”的一個纔是邏輯最小單元,於是可應用樓主表格中的原則排列,而“N多數”之間的順序則是由這“N多數”的宿主決定的,比如是你寫的程序,這個順序由你決定.
剛纔談到了,endian指的是當物理上的最小單元比邏輯上的最小單元小時,邏輯到物理的單元排布關係。咱們接觸到的物理單元最小都是byte,在通信領域中,這裏往往是bit,不過原理也是類似的。
實踐可以給你更多的經驗,比如在一個嵌入式系統的通信協議中,從底層射頻驅動到上層的協議棧全部需要實現,那麼很可能遇到多個endian的問題,底層的bit序、協議層的byte序、應用層的byte序,這些都是不同的概念.
判斷機器是大端還是小端得程序
endianess_judge.c
大端小端轉換程序
endianness_convert.c
文檔鏈接:
http://en.wikipedia.org/wiki/Endianness