天天看點

c語言與endian的親密接觸

; ---------------------------------------------------------------------------

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 的機器可同時運作的代碼。

繼續閱讀