天天看點

[轉帖]Endianness一點通

Endianness 的問題實質就是關于計算機如何存儲大的數值的問題。 我們知道一個基本存儲單元可以儲存一個位元組,每個存儲單元對應一個位址。對于大于十進制255(16進制0xff)的整數,需要多個存儲單元。例如,4660對應于0x1234,需要兩個位元組。不同的計算機系統使用不同的方法儲存這兩個位元組。在我們常用的PC機中,低位的位元組0x34儲存在低位址的存儲單元,高位的位元組0x12儲存在高位址的存儲單元;而在Sun工作站中,情況恰恰相反,0x34位于高位址的存儲單元,0x12位于低位址的存儲單元。前一種就被稱為Little Endian,後一種就是Big Endian。 如何記住這兩種存儲模式?其實很簡單。首先記住我們所說的存儲單元的位址總是由低到高排列。對于多位元組的數值,如果先見到的是低位的位元組,則系統就是Little Endian的,Little 就是"小,少"的意思,也就對應"低"。相反就是Big Endian,這裡 Big "大"對應"高"。 為了加深對Endianness的了解,讓我們來看下面的C程式例子: char a = 1; char b = 2; 位址偏移量 記憶體映像 short c = 255; 0x0000: 01 02 FF 00 long d = 0x44332211; 0x0004: 11 22 33 44 在右側我們可以見到在基于Intel 80x86的系統上的記憶體映像,顯然我們可以馬上判定這一系統是Little Endian的。對于16位的整形數(short)c,我們先見到其低位的0xff,下一個才是0x00。同樣對于32位長整形數(long)d,在最低的位址0x0004存的是最低位位元組0x11。如果是在Big Endian的計算機中,則位址偏移量從0x0000到0x0007的整個記憶體映像将為:01 02 00 FF 44 33 22 11。 所有計算機處理器都必須在這兩種Endian間作出選擇。但某些處理器(如MIPS和IA-64)支援兩種模式,可由程式設計者通過軟體或硬體設定一種Endian。以下是一個處理器類型與對應的Endian的簡表: 純Big Endian: Sun SPARC, Motorola 68000,Java Virtual Machine Bi-Endian, 運作Big Endian模式: MIPS運作IRIX, PA-RISC,大多數Power和PowerPC系統 Bi-Endian, 運作Little Endian模式: MIPS 運作Ultrix,大多數DEC Alpha, IA-64運作Linux Little Endian: Intel x86,AMD64,DEC VAX 如何在程式中檢測本系統的Endianess?可調用下面的函數來快速驗證,如果傳回值為1,則為Little Endian;為0則是Big Endian: int testendian() { int x = 1; return *((char *)&x);} Endianness對于網絡通信也很重要。試想當Little Endian系統與Big Endian的系統通信時,如果不做适當處理,接收方與發送方對資料的解釋将完全不一樣。比如對以上C程式段中的變量d,Little Endian發送方發出11 22 33 44四個位元組,Big Endian接收方将其轉換為數值0x11223344。這與原始的數值大相徑庭。為了解決這個問題,TCP/IP協定規定了專門的"網絡位元組次序",即無論計算機系統支援何種Endian,在傳輸資料時,總是數值最高位的位元組最先發送。從定義可以看出,網絡位元組次序其實是對應Big Endian的。 為了避免因為Endianness造成的通信問題,及便于軟體開發者編寫易于平台移植的程式,特别定義了一些C語言預處理的宏來實作網絡位元組與主機位元組次序之間的互相轉換。htons()和htonl()用來将主機位元組次序轉成網絡位元組次序,前者應用于16位無符号數,後者應用于32位無符号數。ntohs()和ntohl()實作反方向的轉換。這四個宏的原型定義可參考如下(Linux系統中可在netinet/in.h檔案裡找到): #if defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN) #define htons(A) (A) #define htonl(A) (A) #define ntohs(A) (A) #define ntohl(A) (A) #elif defined(LITTLE_ENDIAN) && !defined(BIG_ENDIAN) #define htons(A) ((((uint16)(A) & 0xff00) >> 8) | (((uint16)(A) & 0x00ff) << 8)) #define htonl(A) ((((uint32)(A) & 0xff000000) >> 24) | (((uint32)(A) & 0x00ff0000) >> 8) | (((uint32)(A) & 0x0000ff00) << 8) | (((uint32)(A) & 0x000000ff) << 24)) #define ntohs htons #define ntohl htohl #else #error "Either BIG_ENDIAN or LITTLE_ENDIAN must be #defined, but not both." #endif

繼續閱讀