之所以struct结构体需要对齐的原因,大家都明白是为了提高寻址效率。看了很多资料和文章分析了结构体对齐问题,但是大多数要么讲的有错,要么讲的不清不楚,这里结合自己的分析和理解给出一个总结。
Part I - 参数定义
结构体中的成员可以是不同的数据类型,成员按照定义时的顺序依次存储在连续的内存空间。要弄清楚内存对齐,首先定义两个参数:内存偏移量和对齐参数。
(1)内存偏移量:内存偏移量指的是结构体变量中成员的地址和结构体变量地址的差。举个例子(32-bit windows):
struct exA
{
int a; //第一个成员的地址就是结构体变量的首地址,所以偏移量为:offset1=0
char b; //第二个成员的偏移量是第一个成员的偏移量加上第一个成员的大小,所以偏移量为:offset2=sizeof(a)+ offset1=4
short c; //第三个成员的偏移量是第二个成员的偏移量加上第二个成员的大小,所以偏移量为:offset3=sizeof(b)+ offset2=5
};
(2)对齐参数:系统默认的对齐参数由pack指定,windows下默认值为8,也可以通过“#pragma pack(n)”的方式指定为1,2,4,16(参考:http://msdn.microsoft.com/en-us/library/2e70t5y1.aspx)。但是真正的对齐参数是由pack的值和结构体中变量大小中较小的值来确定的,即:对齐参数=min(pack,sizeof(结构体中某个成员))。还是上面的例子(32-bit windows):
//这里没有指定pack的值,所以pack值为默认值8
struct exA
{
int a; //第一个成员的对齐参数为:min(4,8)=4
char b; //第二个成员的对齐参数为:min(1,8)=1
short c; //第三个成员的对齐参数为:min(2,8)=2
};
同时,上面说的对齐参数是每个成员的对齐参数,我们还需要计算整个结构体变量的对齐参数,其值为每个成员对齐参数中最大的,即:max(a的对齐参数,b的对齐参数,c的对齐参数),这里等于4。
最后,对齐参数的作用就是:对于每个成员的偏移量,需要能够被该成员的对齐参数整除,否则需要把该偏移量补齐到离他最近的对齐参数的倍数(eg. 如果偏移量是5,对齐参数是4,那么偏移量需要补齐到2*4=8;如果偏移量是0,补齐仍然是0);同样地,对于整个结构体来说,也是需要补齐到最近的对齐参数的倍数,只不过这里的对齐参数是结构体的而不是单个成员的。
Part II - 计算方法
弄清楚了上面两个参数,接下来说计算结构体占用内存大小的方法:(1)计算第一个成员的偏移量;(2)按照该成员的对齐参数来计算该成员实际的偏移量;(3)重复上述步骤一直到最后一个成员;(4)最后一个成员的实际偏移量加上最后一个成员的大小得到结构体的大小;(5)按照整个结构体的对齐参数来计算实际的结构体大小。还是举个例子(32-bit windows):
//这里没有指定pack的值,所以pack值为默认值8
struct exA
{
char a;
long b;
double c;
};
Step1:计算a的偏移量,为0。再按照a的对齐参数min(1,8)=1补齐,还是0;
Step2:计算b的偏移量,为0+1=1。再按照b的对齐参数min(4,8)=4补齐,得到4;
Step3:计算c的偏移量,为4+4=8。再按照c的对齐参数min(8,8)=8补齐,得到8;
Step4:计算结构体的大小,为8+8=16。再按照结构体的对齐参数max(1,4,8)=8补齐,还是得到16。所以最终sizeof(exA)=16(VS2008实测正确)。
为了说明成员顺序不同对内存对齐造成的影响,再来个例子(32-bitwindows):
//这里没有指定pack的值,所以pack值为默认值8
struct exB
{
char a;
double c;
long b;
};
Step1:计算a的偏移量,为0。再按照a的对齐参数min(1,8)=1补齐,还是0;
Step2:计算c的偏移量,为0+1=1。再按照c的对齐参数min(8,8)=8补齐,得到8;
Step3:计算b的偏移量,为8+8=16。再按照b的对齐参数min(4,8)=4补齐,得到16;
Step4:计算结构体的大小,为16+4=20。再按照结构体的对齐参数max(1,8,4)=8补齐,得到24。所以最终sizeof(exB)=24(VS2008实测正确)。
如果通过#pragma pack(4)将pack设置为4,则上面sizeof(exA)= sizeof(exB)=16。从这里也可以看出设计结构体时,成员最好按照从小到大的顺序排列。
最后如果是结构体嵌套结构体的情况,则直接展开所有成员,再按上述办法计算。再来个例子(32-bit windows):
//这里没有指定pack的值,所以pack值为默认值8
struct exC
{
int a;
struct
{
char aa;
short bb;
}exD;
char b;
short c;
};
Step1:计算a的偏移量,为0。再按照a的对齐参数min(1,8)=1补齐,还是0;
Step2:直接展开计算aa的偏移量,为0+4=4。再按照aa的对齐参数min(1,8)=1补齐,得到4;
Step3:直接展开计算bb的偏移量,为4+1=5。再按照bb的对齐参数min(2,8)=2补齐,得到6;
Step4:计算b的偏移量,为6+2=8。再按照b的对齐参数min(1,8)=1补齐,得到8;
Step5:计算c的偏移量,为8+1=9。再按照c的对齐参数min(2,8)=2补齐,得到10。
Step6:计算结构体的大小,为10+2=12。再按照结构体的对齐参数max(1,1,2,1,2)=2补齐,得到12。所以最终sizeof(exC)=12(VS2008实测正确)。
Part III - 修改pack
修改pack的值有两种办法:
1、在VS中:打开“项目属性”,在“C/C++”的“代码生成(Code Generation)”选项中将“结构成员对齐(Struct Member Alignment)”修改成想要的值,默认是8字节。
2、使用上面说的#pragma pack(n)指令:
#pragma pack(4) //设置pack为4
struct exA
{
char a;
long b;
double c;
};
#pragma pack() //还原默认设置