结构体对齐

来源:互联网 发布:抱枕淘宝 编辑:程序博客网 时间:2024/06/11 05:39

为什么要引入内存对齐?可以提高存储效率,CPU要访问某个数据,可以减少访问次数,提高访问速度,但是速度的提高会损失些内存,属于空间效率和时间效率的折中问题。

1.内存对齐与编译器设置有关,首先要搞清编译器这个默认值是多少;

2. 如果不想编译器默认的话,可以通过#pragma pack(n)来指定按照n对齐;

3. 每个结构体变量对齐,如果对齐参数n(编译器默认或者通过pragma指定)大于该变量所占字节数(m),那么就按照m对齐,内存偏移后的地址是m的倍数,否则是按照n对齐,内存偏移后的地址是n的倍数。也就是最小化长度规则

4. 结构体总大小: 对齐后的长度必须是成员中最大的对齐参数的整数倍。最大对齐参数是从第三步得到的。

5. 补充:如果结构体A中还有结构体B,那么结构体B的对齐方式是选它里面最长的成员的对齐方式

所以计算结构体大小要走三步,首先确定是当前程序按照几对齐(参照1,2点),接着计算每个结构体变量的大小和偏移(参照3,5),最后计算结构体总大小(参照4)。

先看一个内存对齐的例子:

      struct example1{

           char a;

           double b;

           long l;

};

      struct example2{

           char a;

           long l;

           double b;

};

先算算example1吧,假设编译器是以16对齐的

      1.确定按照几对齐: 16;

      2.确定每个成员的偏移:a 占一个字节,16>1, 按照1对齐,起始位置0,0%1 = 0,那么a就存在0位置;b占8个字节,16>8,按照8对齐,起始位置就不能是1了,因为要按照8对齐,所以最近的偏移起始位置是8, 8%8 =0, 那么b就存在位置8-15的位置;l占4个字节,16>4,按照4对齐,起始位置16, 16%4=0,那么l就存在位置16-19的位置。所以结构体从0到19一共占用20个字节

      3.结构体总大小:成员中最大的对齐参数是b的8对齐,所以20%8!=0, 24刚好。

      真的很搞!同理计算example2应该是16;

     再举个结构体嵌套的例子吧,

#pragma pack(push)

#pragma pack(8)

struct test1{

      int a;

      char b;

      int c[20]

long l;

} ;

struct test2{

      char a1;

      char a2;

      struct test1 t1;

      double b1;

}

#pragma pack(pop)

先计算test1, 8对齐,a占用0-3,b占用4,c占用8-87,l占用88-91,一共92个字节。成员中最大的对齐参数是int了92%4=0;

再计算test2, a1z占用0,a2占用1,t1呢,4 % 4 (test1里面最长的成员的对齐方式) = 0, 4-95,b1占96到103;一共104个字节,成员中最大的对齐参数是double了104%8=0; 所以是104.

-----------------------原文http://www.cppblog.com/iuranus/archive/2009/01/06/71388.html--------------------------------
编译器是按照什么样的原则进行对齐的?

成员对齐有一个重要的条件:即每个成员按自己的方式对齐。其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里默认是8字节)中较小的一个对齐。并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。每个成员的起始地址%每个成员的自身对齐值=0,否则补空直至满足条件。

先让我们看四个重要的基本概念:
1.
数据类型自身的对齐值:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
2.
结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3.
指定对齐值:#pragma pack (value)时的指定对齐值value
4.
数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。


有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是 表示对齐在N,也就是说该数据的存放起始地址%N= 0”而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍,结合下面例子理解)这样就不能理解上面的几个例子的值了。


例子分析:
分析例子B


struct B
{
    char b;
    int a;
    short c;
};


假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0。第二个成员变量a,其自身对齐值为4,所以有 效对齐值也为4,所以只能存放在起始地址为0x00040x0007这四个连续的字节空间中,符合0x0004%4=0,且紧靠第一个变量。第三个变量 c,自身对齐值为2,所以有效对齐值也是2,可以存放在0x00080x0009这两个字节空间中,符合0x0008%2=0。所以从0x00000x0009存放的都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整 的要求,0x00090x0000=10字节,(102)%40。所以0x0000A0x000B也为结构体B所占用。故B0x0000到 0x000B 共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了,因为它的起始地址是0,因此肯定是对齐的。之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一 个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍。其实诸如:对于char型数据,其 自身对齐值为1,对于short型为2,对于intfloatdouble类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了。
同理,分析上面例子C


#pragma pack (2)


struct C
{
    char b;
    int a;
    short c;
};


#pragma pack ()


第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C0x0000开始,那么b存放在0x0000,符合 0x0000%1= 0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x00020x00030x00040x0005四个连续字节中,符合0x0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放在0x00060x0007中,符合 0x0006%2=0。所以从0x00000x00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0C 只占用0x00000x0007的八个字节。所以sizeof(struct C)=8


------------------------原文http://blog.sina.com.cn/s/blog_74a271040100u15p.html------------------------------------------
0 0
原创粉丝点击