一道内存对齐问题和由此引出的CPU字节序问题

 

    一大早晨的群里就很活跃,好现象啊
    一位“同学”发了一道题,问题是b输出什么。

    struct sm
    {
        short a;
        char b,c;
    };
    int a=0x12345678;
    int b=(int)(*(struct sm*)&a).c;

    cout<<b<<endl;

    乍一看,如此麻烦,仔细瞧瞧,也不过是一堆括号。一层一层的拨开,原来就是求a的第四个字节。那当然是0x78了。不对!表忘记多字节的数据在内存里是如何存放的!Intel的机器都是little-endian啊。那就应该是0x12了。上机调试,果然如此,b的输出结果是18。

    由此想到了little-endian和big-endian,于是决定仔细看看。网上看到两篇文章,第一篇讲了比较多的不同字节序的原因,第二篇更是给出了一段W. Richard Stevens的用于判断字节序的代码。都贴过来吧~~

    第一篇

    昨天,看我们的程序员写的一段网络应用中数据接收部分的代码,该段代码用于从一个用C++编写的应用中接收数据,其中有一个数据项是整型数,在代码中是这样做的:接收过来后,对数据流中的这个整型数,做一次字节序的转换,第0个字节跟第3个字节互换,第1个字节跟第2个互换。经过跟程序员的交流,他跟我结实说不这么变换数据不对,具体原因也不知道为什么。

    要把这个问题搞清楚,需要从BIG-ENDIAN、LITTLE-ENDIAN、主机字节序、网络字节顺序和JAVA字节序开始,然后在针对以上问题进行分析。

    1.BIG-ENDIAN、LITTLE-ENDIAN跟多字节类型的数据有关的比如int,short,long型,而对单字节数据byte却没有影响。BIG-ENDIAN就是低位字节排放在内存的低端,高位字节排放在内存的高端。而LITTLE-ENDIAN正好相反。 比如 int a = 0x05060708 在BIG-ENDIAN的情况下存放为: 字节号 0 1 2 3 数据 05 06 07 08 在LITTLE-ENDIAN的情况下存放为: 字节号 0 1 2 3 数据 08 07 06 05

    2.BIG-ENDIAN、LITTLE-ENDIAN、跟CPU有关的,每一种CPU不是BIG-ENDIAN就是LITTLE-ENDIAN、。IA架构的CPU中是Little-Endian,而PowerPC 、SPARC和Motorola处理器。这其实就是所谓的主机字节序。而网络字节序是指数据在网络上传输时是大头还是小头的,在Internet的网络字节序是BIG-ENDIAN。所谓的JAVA字节序指的是在JAVA虚拟机中多字节类型数据的存放顺序,JAVA字节序也是BIG-ENDIAN。

    3.所以在用C/C++写通信程序时,在发送数据前务必用htonl和htons去把整型和短整型的数据进行从主机字节序到网络字节序的转换,而接收数据后对于整型和短整型数据则必须调用ntohl和ntohs实现从网络字节序到主机字节序的转换。如果通信的一方是JAVA程序、一方是C/C++程序时,则需要在C/C++一侧使用以上几个方法进行字节序的转换,而JAVA一侧,则不需要做任何处理,因为JAVA字节序与网络字节序都是BIG-ENDIAN,只要C/C++一侧能正确进行转换即可(发送前从主机序到网络序,接收时反变换)。如果通信的双方都是JAVA,则根本不用考虑字节序的问题了。

    4.如果网络上全部是PowerPC,SPARC和Motorola CPU的主机那么不会出现任何问题,但由于实际存在大量的IA架构的CPU,所以经常出现数据传输错误。

    5.文章开头所提出的问题,就是因为程序运行在X86架构的PC SERVER上,发送数据的一端用C实现的,接收一端是用JAVA实现的,而发送端在发送数据前未进行从主机字节序到网络字节序的转换,这样接收端接收到的是LITTLE-ENDIAN的数据,数据解释自然出错。

    第二篇

谈到字节序的问题,必然牵涉到两大CPU派系。那就是Motorola的PowerPC系列CPU和Intel的x86系列CPU。PowerPC系列采用big endian方式存储数据,而x86系列则采用little endian方式存储数据。那么究竟什么是big endian,什么又是little endian呢?
    字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有小端、大端两种字节顺序。小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处;大端字节序是高字节数据存放在低地址处,低字节数据存放在高地址处。基于X86平台的PC机是小端字节序的,而有的嵌入式平台则是大端字节序的。因而对int、uint16、uint32等多于1字节类型的数据,在这些嵌入式平台上应该变换其存储顺序。通常我们认为,在空中传输的字节的顺序即网络字节序为标准顺序,考虑到与协议的一致以及与同类其它平台产品的互通,在程序中发数据包时,将主机字节序转换为网络字节序,收数据包处将网络字节序转换为主机字节序。
    其实big endian是指低地址存放最高有效字节(MSB),而little endian则是低地址存放最低有效字节(LSB)。
    用文字说明可能比较抽象,下面用图像加以说明。比如数字0x12345678在两种不同字节序CPU中的存储顺序如下所示:
Big Endian
   低地址 高地址
   ----------------------------------------->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   | 12 | 34 | 56 | 78 |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Little Endian
   低地址 高地址
   ----------------------------------------->
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   | 78 | 56 | 34 | 12 |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    从上面两图可以看出,采用big endian方式存储数据是符合我们人类的思维习惯的。而little endian,!@#$%^&*,见鬼去吧 -_-|||
    为什么要注意字节序的问题呢?你可能这么问。当然,如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。但是,如果你的程序要跟别人的程序产生交互呢?在这里我想说说两种语言。C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而JAVA编写的程序则唯一采用big endian方式来存储数据。试想,如果你用C/C++语言在x86平台下编写的程序跟别人的JAVA程序互通时会产生什么结果?就拿上面的0x12345678来说,你的程序传递给别人的一个数据,将指向0x12345678的指针传给了JAVA程序,由于JAVA采取big endian方式存储数据,很自然的它会将你的数据翻译为0x78563412。什么?竟然变成另外一个数字了?是的,就是这种后果。因此,在你的C程序传给JAVA程序之前有必要进行字节序的转换工作。
    无独有偶,所有网络协议也都是采用big endian的方式来传输数据的。所以有时我们也会把big endian方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。ANSI C中提供了下面四个转换字节序的宏。
    这里有一段W. Richard Stevens的代码,用于判断字节序
/*
* gcc -Wall -pipe -O3 -s -o byte_order byte_order.c
*/

#include <stdio.h>
#include <stdlib.h>
/*
* return value:
* 1 big-endian
* 2 little-endian
* 3 unknow
* 4 sizeof( unsigned short int ) != 2
*/

static int byte_order ( void )
{
    union
    {
        unsigned short int s;
        unsigned char c[ sizeof( unsigned short int ) ];
    } un;
    un.s = 0x0201;
    if ( 2 == sizeof( unsigned short int ) )
    {
        if ( ( 2 == un.c[0] ) && ( 1 == un.c[1] ) )
        {
            puts( "big-endian" );
            return( 1 );
        }
        else if ( ( 1 == un.c[0] ) && ( 2 == un.c[1] ) )
        {
            puts( "little-endian" );
            return( 2 );
        }
        else
        {
            puts( "unknow" );
            return( 3 );
        }
    }
    else
    {
        printf( "sizeof( unsigned short int ) = %u/n", ( unsigned int )sizeof(unsigned short int ) );
        return( 4 );
    }
    return( 3 );
} /* end of byte_order */
int main ( int argc, char * argv[] )
{
    printf( "byte_order() = %d/n", byte_order() );
    return( EXIT_SUCCESS );
} /* end of main */

 发表于: 2008-08-22,修改于: 2008-08-22 10:40 已浏览64次,有评论0条 推荐 投诉
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值