自第一台電腦誕生,其最小儲存單元就被永久的定格了——一個由8個位元(bit)組成的稱為位元組(byte)的單位。電腦的所有記憶體以位元組數組的方式進行編址。
當一個邏輯上長於一個位元組的整形資料放置在記憶體中時(比如16位,32位,和64位的整數),電腦設計者需要考慮這些位元組的儲存順序。一些體繫結構的設計者選擇了將位元組的邏輯順序與物理順序一致,即將邏輯上較低的位元組放置在物理上較低的位元組上;另外一些設計者則選擇了將位元組的邏輯順序與物理順序相反,即將邏輯上較低的位元組放置在物理上較高的位元組上。前者被稱為“little endian”,比如Intel x86系列;後者則被稱為“big endian”,比如Motorola的PowerPC以及Sun Sparc。還有一些平台同時支援兩種方案,由開發人員決定使用哪一種。
兩種選擇為底層開發人員帶來了一定的困擾。比如,兩個位元組順序不一致的平台之間進行通訊,或者在兩個位元組順序不一致的平台之間移植系統。這都是跨平台的例子,對於這些情況,位元組順序的問題是不能迴避的。對於僅僅在一種平台上進行開發的程式員而言,如果它能夠避免強制類型轉換(比如將位元組數組強制轉換為一個長整數),一貫的以邏輯順序來操作大於一個位元組的整數,應該可以迴避這個問題。但由於C語言是一種非常靈活的語言,有時候通過強制類型轉換可以讓代碼非常精簡,甚至達到非常巧妙的效果,所以,要求C程式員完全迴避這個問題,幾乎是不現實的。
由於Little Endian提供了邏輯順序與物理順序的一致性,讓編程者擺脫了不一致性所帶來的困擾,C語言開發人員可以無所顧忌的按照自己的意願進行強制類型轉換,所以現代體繫結構幾乎都支援Little Endian。但Big Endian也有其優點,尤其對於組譯工具員:他們對於任意長度的整數,總是可以通過判斷Byte 0的bit-7來查看一個整數的正負;對於Little Endian則不得不首Crowdsourced Security Testing道當前整數的長度,然後查看最高byte的bit-7來判斷其正負。對於這種情況,big endian的開發人員可以寫出非常高效的代碼。
兩派的支援者爭論不休,正像他們所支援名詞(big endian和little endian)的典故所講述的那樣:Little Endian和Big Endian這兩個名詞來源於Jonathan Swift的《格利佛遊記》其中交戰的兩個派別無法就應該從哪一端--小端還是大端--開啟一個半熟的雞蛋達成一致。:)在那個時代,Swift是在諷刺英國和法國之間的持續衝突,Danny Cohen,一位網路通訊協定的早期開創者,第一次使用這兩個術語來指代位元組順序,後來這個術語被廣泛接納了(摘自《深入理解電腦系統》)。
需要特別指出的是,通常所提到的Little Endian和Big Endian僅僅指位元組順序。在硬體設計者的術語中,對於一個位元組內部的bit順序也分Little Endian和Big Endian,但對於程式員而言,這些bit順序的不同是透明的,也就是說,程式員只需要按照邏輯順序來看待和操作位元組內部的bit即可。
Endian的不同不僅僅帶來位元組順序的不同,還有更多的問題。如果C程式員在定義一個結構體時,使用了bitwise的域定義,比如:
struct foo {
int a:3;
int b:7;
int c:13;
int d:9;
};
這個結構體的一個對象會佔用4個位元組。由於a,b,c,d的類型都是int,所以他們都在以int32為單位的整數上分配bit,另外,由於他們的bit數量正好等於int32的bit數,所以,它們都分配於一個int所佔用的空間。關鍵問題在於這些位元組在這4個位元組內是分配順序是怎麼樣的?
對於little endian,其分配順序與邏輯順序是一致的,即在byte[0]的bit[0~2]上分配a,在byte[0]的bit[3,7]以及byte[1]的bit[0,1]上分配b,依次類推。
對於big endian,其方案會帶來很大的問題。其分配順序為:
位元組物理順序:從低到高;
位元組內bit順序:從高到底;
也就是說,big endian在bitwise的分配方案上,從位元組順序到bit順序都反過來了(因為其正向儲存順序為:位元組從高到底,bit從低到高(從程式員的觀點看))。換句話說:big endian的bit分配順序為,按照bit的邏輯順序,從高到底進行分配。
|--------|--------|--------|--------|
Logical Byte Order | byte 3 | byte 2 | byte 1 | byte 0 |
|--------|--------|--------|--------|
Physical Byte Order | byte 0 | byte 1 | byte 2 | byte 3 |
|--------|--------|--------|--------|
Bitwise allocation |-a-|---b---|------c------|----d----|
請注意,並不是硬體平台使用的這種方案,而是C語言編譯器。這是一種荒謬的方案,我想可能是C語言編譯器的早期開發人員希望通過編譯器屏蔽掉big endian和little endian在bitwise allocation上的差異,而都與實體儲存體順序一致。但由於其採用了bit order的反向分配,反而加劇了這種差異,隨後的編譯器為了保持相容,也只好將錯誤延續了下來。
基於這種原因,在C語言中直接使用bitwise的方式定義結構體是一種危險的方式,因為這些代碼是平台依賴的。當進行跨平台移植的時候必須重新定義這些結構體。
有兩種方式可以消除這種風險:
1、使用邏輯移位的方式來操作bit。以上面的例子為例,我們可以這麼做:
struct foo {
int value;
};
#define SET_A(f,a) do { (f) |= ((a)&0x7); } while(0)
#define SET_B(f,b) do { (f) |= (((b)&0x7F)<<3); } while(0)
#define SET_C(f,c) do { (f) |= (((c)&0x1FFF)<<10); } while(0)
#define SET_D(f,d) do { (f) |= (((d)&0x1FF)<<23); } while(0)
#define GET_A(f) ((f)&0x7)
#define GET_B(f) (((f)>>3)&0x7F)
#define GET_C(f) (((f)>>10)&0x1FFF)
#define GET_D(f) (((f)>>23)&0x1FF)
2、對於big endian,我們可以使用相反的順序來聲明bitwise fields。仍然以上例為例:
#if LITTLE_ENDIAN
#define BITWISE(type,a,b,c,d) type a, b, c, d
#else
#define BITWISE(type,a,b,c,d) type d, c, b, a
#endif
struct foo {
BITWISE(int, a:3, b:7, c:13, d:9);
};
對於little endian,邏輯順序與物理順序一致,只需要按照原樣定義;而對於big endian,由於其整體的bit順序恰好與邏輯順序是相反的,所以,我們將順序反過來,使其bit的分配順序與邏輯順序一致即可。
來源文件 <http://hi.baidu.com/huoyanliu/blog/item/add5ebdc6f5370aacc116623.html>