; ---------------------------------------------------------------------------
Author: hjjdebug
Time: 2009-8-5 10:35
FileName: c語言與endian的親密接觸.txt
; ---------------------------------------------------------------------------
問題提出:
在intel x86下編寫了一段c 程式(實際是将一段彙編代碼反彙編成c)
在x86 下調試正常。然後拿到powerpc 下,編譯,運作,結果錯誤!
問題分析:
原則上,沒有系統調用的c函數隻要重新編譯就可以運作,問題出在哪呢?
問題是由位元組序引起的,x86是小端序,powerpc 是大端序。
分析1:
如果你在程式中聲明了BYTE(char), WORD(short), DWORD(int)變量。
BYTE 沒有位元組序問題,WORD,DWORD有位元組序,但如果WORD,DWORD僅限于
本變量範圍,那麼結果應該也是正确的,為什麼出現錯誤呢?
分析2:當一個用BYTE 存儲的數組,被當成DWORD 來引用的時候,或者反之,
就會出現問題。是以,必需要避免此類事情的發生。
也就是說,如果你的資料是用BYTE 聲明的,使用時用BYTE *,
如果你的資料是用INT 聲明的,使用時用INT *,不要強制指針轉換,
不要發生混淆! 否則對不同endial 的機器,會取錯資料。
問題結果:參見問題分析。
編寫不同endian 程式的注意事項或解決方案。
1. byte, int 的使用不要混淆
2. 對于必需要統一字序的情況,可以使用htonl 來轉換。即将主機序轉換為網絡序(big endian)。
hton 即不屬于标c函數,也不屬于winapi,而是屬于socket 程式設計。說起來很神秘,下面給出它的一種實作。
u_long htonl( u_long hostlong )
{
long ul;
hostlong_byte(hontlong,(BYTE *)ul);
return ul;
}
同理,可以寫出ntohl 函數。但需要netlong_byte 函數的支援。
那麼,hostlong_byte 到底完成什麼功能呢?
它完成如果是little endian, 則 b[0]=hl&0xff, b[1]=(hl>>8)&0xff,b[2]=(hl>>16)&0xff,b[3]=(hl>>24)0xff
如果是big endian, 則 b[3]=hl&0xff, b[2]=(hl>>8)&0xff,b[1]=(hl>>16)&0xff,b[0]=(hl>>24)0xff
那麼,我們是否需要endian 的判斷呢? 不用!!!! 看下面。
void hostlong_byte(unsigned int *hl, unsigned char *bytes)
{
*bytes++ = ((*hl)>>24)&0xff;
*bytes++ = ((*hl)>>16)&0xff;
*bytes++ = ((*hl)>>8)&0xff;
*bytes++ = ((*hl))&0xff;
}
該函數在little endian 上,實作了位元組反轉。在big endian上,實作了位元組拷貝。
實際上實作了hostlong to netlong 的轉換,真是耐人尋味!實際上是編譯器之功!
奧妙在于: hl 整數指針總是指向位址開始(低),*hl 是取到整數數值, little-endian, big-endian機器會以不同的次序組裝資料。
(*hl)>>24 是取到資料的高位8bits, (*hl)>>24 &0xff 寫法更嚴謹,保證隻取一個byte, 結果是整數最高位元組放到記憶體低位址,最低位元組放到記憶體高位址。
結果,little-endian 上實作了位元組copy, big-endian 上實作位元組反轉。
同理,實作netlong to hostlong 的轉換如下: 我們還是把netlong 想象成固定的位元組數組。
void byte_hostlong(unsigned int *hl, unsigned char *bytes)
{
*hl = (bytes[0]<<24) + (bytes[1]<<16) + (bytes[2]<<8) + (bytes[3]);
}
該函數在little endian 的機器上,實作了位元組反轉,在big endian 上,實作了位元組copy.
3. 還有一個較容易想到的辦法是利用編譯宏控制。 這是另外一種辦法。ifdef X86{}else{}
隻是編出的代碼将不具有通用性。必須有源碼,在不同環境下重新編譯即可。
#ifdef _NOT_INTEL_CPU_
#define SWAP_16(a) a = ((unsigned short)(a)>>8)|((unsigned short)(a)<<8);
#define SWAP_32(a) swap_32((unsigned long&)a);
inline void swap_32(unsigned long &a)
{
a = ((unsigned)(a)>>24)|(((unsigned)(a)>>8)&0xFF00)|(((unsigned)(a)<<8)&0xFF0000)|((unsigned)(a)<<24);
}
#else
#define SWAP_16(a)
#define SWAP_32(a)
#define SWAP_FLOAT(b)
#endif
4. 什麼情況下必須要統一位元組序。
當然是與位元組有關的操作。
容易想到的是在網絡上傳送資料。
另一種情況是在進行整型資料運算時,用到左移,右移指令。例如左移3位,左移4位,必須
要統一位元組序,否則在不同endian 上會形成不同的資料。如果隻是普通的加減乘除,與或與位元組無關的操作
則不需要統一位元組序。
調試吧,調試是一種真手段! 才能保證不同endian 的機器可同時運作的代碼。