天天看点

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 的机器可同时运行的代码。

继续阅读