结构体的对齐
在sizeof计算一个结构体的大小时,经常得到的值比结构体内部成员所占内存总和要大,这就是因为在结构体内部,成员在存储时有对齐的规则。结构体对齐指的是:编译器向结构体插入无用内存的能力,插入无用内存使得结构体成员以最佳方式对齐,从而得到更高的效能。当基本数据类型以字节地址(几倍于自身大小)存储时,很多处理器能够获得最佳效能。
以下是几个例子:
Struct x
{
Shorts;//2 bytes and 2 padding tytes
Inti; //4 bytes
Charc; //1 bytes and 3 padding bytes
};
Struct y
{
Inti; //4 bytes
Charc ; //1 bytes and 1 padding byte
Shorts; //2 bytes
};
Struct z
{
Inti; //4 bytes
Shorts; // 2 bytes
Charc; //1 bytes and 1 padding byte
};
Sizeof(x)=12; sizeof(y)=8; sizeof(z)=8;
X的内存布局: s I c
11** 1111 1***
Y的内存布局: I c s
1111 1* 11
Z的内存布局: I s c
1111 11 1*
其中*表示填充的字节,x中s后面为什么要填充两个字节?因为i是整型,其起始位置要为4的倍数。C后面要填充3个字节,因为结构体size要为4(即最大类型——整型sizeof(int))的倍数。
Y中C后面填充一个字节,因为s为short类型,起始位置要为2的倍数。S后面没有填充,因为c和s正好占用了4个字节。
Z中s后面没有填充,因为s和c正好占用4个字节,c填充一个字节因为struct大小要为int的整数倍。
再看一个有结构体作为成员的例子:
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的倍数,补齐,所以为24。Sizeof(B)=48;看一下B的布局
B的内存布局:e f g h i
11** 1111 11111111 11****** 1111****, 11111111,1111****
I其实就是A的内存布局。I的起始位置要为24的倍数,所以h后面要补齐(A的最大类型是double,占8个字节,所以i开始要8字节对齐,即8的倍数,所以h要填充)。
通过把最大的数据类型放在结构体的开始,最小数据类型放在结构体的最后,这样可以得到最小的结构体size。
通过上面的例子可以总结一下三个规律:
1 数据成员对齐,结构体(或联合体)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
2 结构体作为成员,如果一个结构体里面有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储(struct b里面有struct a,a里面有char,int,double等元素,那么a应该从8的整数倍开始存储)。
3 结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。
还有一种常见情况,结构体中含有位域字段。位域成员不能单独被取sizeof值。规定int,unsigned int和 bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其他类型的存在。使用位于的主要目的是压缩存储,其大致规则:
1 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,知道不能容纳为止;
2 如果相邻字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;
4 如果位域字段之间穿插着非位域字段,则不进行压缩。
5 整个结构体的总大小为最宽基本类型成员大小的整数倍。
看下面例子:
struct A{
short f1:3;
short f2:4;
short f3:10;
}; A的布局为 f1 f2 f3
111 1111*** **(11个*) 1—1(10个1)**(6个*)位域类型为short,第一个字节能容纳f1和f2,所以f2被压缩到第1个字节中,而f3只能从下一个字节开始。因此sizeof(A)=2。
Struct B{
Char f1:3;
Short f2:4;
Char f3:5;
};相邻位域的类型不同,在VC6中sizeof为6,在Dev-C++中为2.
Struct C{
Char f1:3;
Char f2;
Char f3:5;
};非位域字段插在其中,不会产生压缩,大小为3。
补充一下联合体:联合体的成员共享一片内存空间,联合体的成员可以使任何数据类型,用来存储联合体的字节数至少要能够存储最大的 成员。多数情况下联合体包含两种或多种数据类型,但同一时间只能引用一个成员。
例如联合体:union AA{
short a;
float b;
}; sizeof(AA)=sizeof(float)=4; 某一时刻对a赋值 AA.a=20; 以后引用过程只能用AA.a 。例如 printf("AA.a=%d\n",AA.a);则输出AA.a=20。 如果这样使用 printf("AA.b=%f\n",AA.b);则输出结果是不确定的。同样,如果某时刻对b赋值 AA.b=2.4,以后只能引用AA.b。printf("AA.b=%f\n",AA.b);则输出2.4;如果printf("AA.a=%d\n", AA.a);则输出不确定。