big-endian和little-endian的判断和引申

来源:互联网 发布:如何装修淘宝店铺首页 编辑:程序博客网 时间:2024/04/30 22:53

  经常在C/C++笔试题中见到这样的程序题:编写一个函数来判断处理器是big-endian(大端)还是little-endian(小端)的,如果是大段则返回0,小断则返回1。 那么究竟什么是big-endian和little-endian呢?

 这是指内存中跨越多个字节的数据的存放顺序,或者说是cpu对操作数的存放顺序,简而言之:
Big endian machine: It thinks the first byte it reads is the biggest.
Little endian machine: It thinks the first byte it reads is the littlest.

就是说采用big-endian模式的cpu对操作数的存放方式是低位地址存放低字节,而采用little-endian的cpu对操作数的存放方式是地位地址存放高字节,比如要存放的操作数是0Xabcd1234,存放在起始地址为0X00000000,那么它在big-endian和little-endian中存储的情况分别是:

                     big-endian     little-endian
0x00000000     0xab                0x34

0x00000001     0xcd                0x12

0x00000002     0x12                0xcd

0x00000003     0x34                0xab

同样,如果一个四字节数据在内存中的存放方式是这样的话:
0x00000000     0xab                

0x00000001     0xcd               

0x00000002     0x12                

0x00000003     0x34 

那么采用big-endian模式的cpu从0x00000000开始读取一个四字节的变量则会读出0xabcd1234,而采用little-endian模式的机器则会读出0x3412cdab。

回到一开始提到的笔试题,如何用一段程序判断处理器采用的是大端还是小端的存取模式呢,最容易想到的是存储一个int型数据(在标准C中是双字节,但现在大多数编译器定位4个字节,C++中没有明确说明,只是规定了short<=int<=long),然后取它的低字节地址看看存放的是什么就知道了:

int  checkCpu()

{

    int i=1;

    char* pTemp=(char*)(&i);

    return (*pTemp==1);     //true的int值为1,

}

另一种方法是,利用联合类型Union的所有成员都是从低地址开始存放且所有数据成员共用一块空间(任何时刻都只有一个active member),所以有如下程序可以做出判断:

int  checkCpu()

{

    union u

    {

        int    a;

        char b;

    }c;

    c.a=1;

    return (c.b==1);

}

PS:  Intel的机器(X86平台)一般采用小端。
IBM, Motorola(Power PC), Sun的机器一般采用大端。
当然,这不代表所有情况。有的CPU即能工作于小端, 又能工作于大端, 比如ARM, Alpha,摩托罗拉的PowerPC。 具体情形参考处理器手册。

 

由刚才提到的int所占字节数引申出题1:已知一个整形数组a,求数组a中元素的个数。

正解是:sizeof(a)/sizeof(int) 或者sizeof(a)/sizeof(*a)。

而sizeof(a)求得的是数组的大小既所占字节数。

 

由刚才提到的联合类型union的特点引申出概念1。union和struct的区别:

1。union和struct都是由多个不同的数据类型成员组成,不同的是在任一时刻,union中只存放了一个被选中的成员(即active member,最后被复制从而激活的成员),而struct中所有的成员都存在,就是说在struct中,各成员都占有自己的内存空间,它们是同时存在的,而union中所有成员不能同时占用内存空间,一个union的空间大小只是它所有的数据成员中最大的那个。

2。对union中的某个成员变量进行复制,将会对其他成员进行重写(因为所有成员共享一块内存空间),原来成员的值就不存在了;而struct则不是。

 

由概念1引申出的计算union和struct的大小所涉及的字节对齐问题:

  学习过计算机组成原理的朋友知道cpu每次读取数据的时候都是从某个内存地址开始读取的,理论上可以从任意地址开始读取数据,但由于读取效率的问题一些数据需要从特定的内存地址开始访问,如果不按照某些平台对某些数据的读取特点而对数据的存储方式进行设定的话势必会在存取效率上带来损失,这样才有了字节对齐的问题。比如,很多平台都是从偶地址开始读取数据,如果一个int型4字节数据存放在偶地址开始的内存单元的话,那么一个读周期就可以读出,如果放在奇地址的话,那就要两个读周期,并对两次读取的结果的高低字节进行拼凑才能得到想要的数据,所以,也可以说字节对齐提高存取效率是空间换取了时间。而这种对齐是由编译器做的,也就是说,我们可以用一些预编译指令告诉编译器要采取的对齐方式,所以说,这种用字节对齐的方式对cpu存取效率的优化是编译器做的。

字节对齐的细节和编译器的设定有关,但一般而言满足以下三个原则:

1。结构体变量的首地址能够被其最宽数据成员的大小所整除。

2。结构体每个成员相对于结构体首地址的偏移量都是成员自身大小的整数倍,如有需要编译器会在成员之间进行字节的填充。

3。结构体的总大小为结构体中最宽成员的整数倍,如有需要编译器会在最后一个数据成员后面进行字节填充。

所以下面的两个结构体

struct A{        

    int     a;

    char  b;

    short c;

};

sizeof(struct A)=8;// 注意c的偏移地址

struct B{

    char  b;

    int    a;

    short c;

};

sizeof(struct B)=12;//注意a的偏移地址和c后面的字节填充

除编译器默认的字节对齐方式之后,我们还可以采用预编译指令 #progma pack(value) 进行字节对齐值的指定代替缺省的。而struct中每个成员的偏移值则是去自身大小和value中较小的那个,而类或者struct自身的大小要去的整数倍的基数也是最大数据成员的自身大小和value中较小的一个。比如:

#progma pack(push)//将当前pack设置压栈保存;

#progma pack(2)//必须在结构体之前使用,设置接下来结构体的字节对齐方式

struct S1
{
  char c;
  int    i;
};

sizeof(S1)=6;//i的偏移地址以value=2为倍数;

struct S3
{
  char c1;
  S1    s;
  char c2;
};

sizeof(S3)=10;//C2后填充一个字节的数据使S3总大小满足value的整数倍。

PS:空结构体的大小不是0,而是1,因为一个不占空间的变量是不能被存取的,而且如果不占空间的话空结构体之间也无法得到区分,所以编译器会为空结构体分配一个字节。

而对于union来说,它的大小取决于它所有数据成员中占用空间最大的那个,比如:

union A

{

    double  a;

    int         b;

};

sizeof(A)=8;//sizeof(double)=8;

union B

{

    char  a[13];

    char  b;

};

sizeof(B)=13;

union C

{

    char  a[13];

    int     b;

};

sizeof(C)=16;//由于int成员的存在使得union的对齐值为4,所以13->16;

  关于为什么要注意字节对齐的问题除了关乎到计算机的存取效率外,在网络编程中,在不同平台之间(比如在windows和Linux之间)传递二进制流(比如结构体),那么在这两个平台必须定义相同的对齐方式,否则会出一些难以排查的错误。

 

 

 

原创粉丝点击