大小端問題
最近工作中,有兩次遇到大小端問題,所以花時間寫這篇日誌,總結一下。
(1) 前段時間寫了一個修復損壞的gzip文件的tool,在Linux Server上編譯運行沒有問題。但是在Solaris Server上運編譯運行,結果總是和預期的不一致,跟蹤發現是由大小端問題導致的;
(2) 最近在寫一個跨平臺的編譯腳本,編譯參數裏有目標可執行程序運行平臺大小端這個參數;
2. 大小端解析
端模式出自Jonathan Swift書寫的《格列佛遊記》一書,這本書根據將雞蛋敲開的方法不同將所有的人分爲兩類,從圓頭開始將雞蛋敲開的人被歸爲Big Endian,從尖頭開始將雞蛋敲開的人被歸爲Littile Endian。小人國的內戰就源於吃雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開。
在計算機業Big Endian和Little Endian也幾乎引起一場戰爭。在計算機業界,Endian表示數據在存儲器中的存放順序。
大端:高位存在低地址,低位存在高地址;
小端:高位存在高地址,低位存在低地址;(intel的x86,ARM普遍都是屬於小端)
舉個例子,從內存地址0x0000開始有以下數據
0x0000 0x12
0x0001 0x34
0x0002 0xab
0x0003 0xcd
如果我們去讀取一個地址爲0x0000的四個字節變量:
若字節序爲big-endian,則讀出結果爲0x1234abcd;
若字節序位little-endian,則讀出結果爲0xcdab3412.
如果我們將0x1234abcd寫入到以0x0000開始的內存中,則結果爲:
big-endian little-endian
0x0000 0x12 0xcd
0x0001 0x23 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12
Intelx86系列以及ARM系列CPU都是little-endian的字節序.
3. 大小端問題的解決
(1) 下面貼一個很簡單的判斷大小端的函數
int checkCPUendian()//返回1,爲小端;反之,爲大端;
{
union
{
unsigned int a;
unsigned char b;
}c;
c.a = 1;
return 1 == c.b;
}
(2) 大端模式處理器的字節序到網絡字節序不需要轉換,此時ntohs(n)=n,ntohl =n;而小端模式處理器的字節序到網絡字節必須要進行轉換(同理,有時候需要將大端字節順序轉換成小端字節順序,也用這個函數,因爲這個函數本來就是用來顛倒字節順序的),轉換如下:
#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_t)(A) & 0xff00) >> 8 ) | \\
(((uint16_t)(A) & 0x00ff) << 8 ))
#define htonl(A) ((((uint32_t)(A) & 0xff000000) >> 24) | \\
(((uint32_t)(A) & 0x00ff0000) >> 8 ) | \\
(((uint32_t)(A) & 0x0000ff00) << 8 ) | \\
(((uint32_t)(A) & 0x000000ff) << 24))
#define ntohs htons
#define ntohl htohl
#else
#error Either BIG_ENDIAN or LITTLE_ENDIAN must be #defined, but not both.
#endif