struct结构体字节对齐原则及为什么要对齐?

707 阅读6分钟

blog.sina.com.cn/s/blog_14ec…

blog.csdn.net/zhanghow/ar…

//--------------------------------------------------------

本文目录结构

|-为什么结构体内存对齐

|-结构体内存对齐规则

|-具体举例

//--------------------------------------------------------

一、为什么结构体内存对齐

     现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。

       其实我们都知道,结构体只是一些数据的集合,它本身什么都没有。我们所谓的结构体地址,其实就是结构体第一个元素的地址。这样,如果结构体各个元素之间不存在内存对齐问题,他们都挨着排放的。对于32位机,32位编译器(这是目前常见的环境,其他环境也会有内存对齐问题),就很可能操作一个问题,就是当你想要去访问结构体中的一个数据的时候,需要你操作两次数据总线,因为这个数据卡在中间,如图:

struct结构体字节对齐

       在上图中,对于第2个short数据进行访问的时候,在32位机器上就要操作两次数据总线。这样会非常影响数据读写的效率,所以就引入了内存对齐的问题。另外一层原因是:某些硬件平台只能从规定的地址处取某些特定类型的数据,否则会抛出硬件异常。

 

二、结构体内存对齐的规则【未指定#pragma pack时】

      a.第一个成员起始于0偏移处;

      b.每个成员按其类型大小和指定对齐参数n中较小的一个进行对齐;

      c.结构体总长度必须为所有对齐参数的整数倍;

      d.对于数组,可以拆开看做n个数组元素。

     下表是Windows XP/DEV-C++和Linux/GCC中基本数据类型的长度和默认对齐模数。 

  charshortintlongfloatdoublelong longlong double
Win-32长度12444888
模数12444888
Linux-32长度124448812
模数12444444
Linux-64长度124848816
模数124848816

 

三、具体举例

例1:struct {
short a1;
short a2;
short a3;
}A;
struct{
long a1;
short a2;
}B;

sizeof(A) = 6; 这个很好理解,三个short都为2。

sizeof(B) = 8; long为4,short为2,整个为8,因为规则b。

例2:struct A{
int a;
char b;
short c;
};
struct B{
char b;
int a;
short c;
};

sizeof(A) = 8;sizeof(B) = 12。

深究一下,为什么是这样,我们可以看看内存里的布局情况。

                             a         b         c
A的内存布局:1111,     1*,       11

                             b          a        c
B的内存布局:1***,     1111,   11**

     其中星号*表示填充的字节。A中,b后面为何要补充一个字节?因为c为short,其起始位置要为2的倍数,就是原则1。c的后面没有补充,因为b和c正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。B中,b是char为1,b后面补充了3个字节,因为a是int为4,根据原则1,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数,c后面不补充,整个B的空间为10,不符,所以要补充2个字节。

再看两个结构中含有结构成员的例子:

例3:struct A{
int a;
double b;
float c;
};

                struct B{
char e[2];
int f;
double g;
short h;
struct A i;
};

       sizeof(A) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24。

       sizeof(B) = 48; 看看B的内存布局。

                        e         f             g                h                                    i 
B的内存布局:11* *,   1111,   11111111, 11 * * * * * *,        1111* * * *, 11111111, 1111 * * * *

i其实就是A的内存布局。i的起始位置要为24的倍数,所以h后面要补齐。

例4:struct S1  

           {  

              short a;  

              long b;  

           };  

     struct S2  

          {  

    char c;  

    struct S1 d;  

    double e;  

          };  

       在Dev c++中,默认的是8字节对齐。我们分析下在4字节对齐的情况下输出的是,S2是20,S1是8,分析如图:

struct结构体字节对齐

      在4字节对齐的情况中,有一个问题值得注意:就是图中画1的地方。这里面本应short是可以上去的。但是对于结构体中的结构体一定要十分警惕,S1是一体的,short已经由于long进行了内存对齐,后面还空了两个字节的内存,其实此时的short已经变成了4个字节了!!!即结构体不可拆,不管是多少字节对齐,他们都是一体的。所有的圈都变成了叉。所以说结构体只能往前篡位置,不能改变整体。

      我们在分析一些8字节对齐的情况,如图:

struct结构体字节对齐

       以上讲的都是没有#pragma pack宏的情况,如果有#pragma pack宏,对齐方式按照宏的定义来。比如上面的结构体前加#pragma pack(1),内存的布局就会完全改变。sizeof(A) = 16; sizeof(B) = 32;

      有了#pragma pack(1),按1字节对齐。没错,这不是理想中的没有内存对齐的世界吗。

                               a                b             c
A的内存布局:1111,     11111111,   1111

                             e        f             g         h                     i
B的内存布局:11,   1111,   11111111, 11 ,            1111, 11111111, 1111

      那#pragma pack(2)的结果又是多少呢?#pragma pack(4)呢?留给大家自己思考吧,相信没有问题。