内存中的字节对齐

来源:互联网 发布:网络彩票平台出租骗局 编辑:程序博客网 时间:2024/06/04 01:29

 

一、什么是字节对齐,为什么要对齐?

 

现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

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

 

 

二、.字节对齐的原则

先让我们看四个重要的基本概念:

1.基本数据类型自身的对齐值:

 对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。

2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。

3.指定的对齐值:#pragma pack (value)时的指定对齐值value

4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。

 

有效对齐值N决定数据存放的首地址,有效对齐N,是说存放该数据的起始地址%N=0".即是N的倍数。而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度是结构体有效对齐值的整数倍,

 

Win32平台下的微软编译器(cl.exe for 80×86)的对齐策略:

1)结构体变量的首地址是其有效对齐值的整数倍

备注:编译器在给结构体开辟空间时,首先计算结构体的有效对齐值,然后寻找是其有效对齐值整数倍的内存地址,作为结构体的首地址。

2)结构体的每个成员相对于结构体首地址的偏移量(offset)是成员的有效对齐值的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);

备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的有效对齐值得整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。

3)结构体的总大小为结构体的有效对齐值的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。

 

 

在设计结构体的时候,一般会遵照一个习惯,就是把占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对节约一些对齐空间。

 

示例:

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所占用。故B0x00000x000B共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了,因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.

 

#pragma pack (2) /*指定按2字节对齐*/

struct C

{

   char b;

   int a;

   short c;

};

#pragma pack () /*取消指定对齐,恢复缺省对齐*/

Sizeof(struct C)=8;

三、含有位域字段的结构体的对齐方式

结构体中含位域字段。位域成员不能单独被取sizeof值。C99规定intunsigned intbool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。

使用位域的主要目的是压缩存储,其大致规则为:
1)
如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2)
如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3)
如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
4)
如果位域字段之间穿插着非位域字段,则不进行压缩;
5)
整个结构体的总大小为最其有效对齐值的整数倍。

 1struct A{
                      char f1 : 3;
                     char f2 : 4;
                     char f3 : 5;
                     };

                     a        b             c
       A
的内存布局:111,    1111 *,   11111 * * *

      位域类型为char,第1个字节仅能容纳下f1f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(A)的结果为2

2struct B{
                    char f1 : 3;
                    short f2 : 4;
                    char f3 : 5;
                    };

      由于相邻位域类型不同,在VC6中其sizeof6,在Dev-C++中为2

3struct C{
                     char f1 : 3;
                     char f2;
                    char f3 : 5;
                    };

      非位域字段穿插在其中,不会产生压缩,在VC6Dev-C++中得到的大小均为3

 

 

四、如何修改编译器的默认对齐值?

1、在编码时,可以这样动态修改

使用伪指令#pragma pack (n)C编译器将按照n个字节对齐。

使用伪指令#pragma pack (),取消自定义字节对齐方式。

 

2、 vs2008中,可以这样修改:

假设新建的项目为C-test

选择项目----C-test属性,如下图所示

选择代码生成结构体成员对齐