【正确答案】
【答案解析】struct是一种复合数据类型,其构成元素既可以是基本数据类型,如int、double、float、short、char等,也可以是复合数据类型,如数组、struct、union等数据单元。
一般而言,struct的sizeof是所有成员对齐后长度相加,而union的sizeof是取最大的成员长度。
在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
字节对齐也称为字节填充,它是C++编译器的一种技术手段,主要是为了在空间与复杂度上达到平衡。简单地讲,是为了在可接受的空间浪费的前提下,尽可能地提高对相同运算过程的最少(快)处理。字节对齐的作用不仅是便于CPU的快速访问,使CPU的性能达到最佳,而且可以有效地节省存储空间。例如,32位的计算机的数据传输值是4字节,64位计算机数据传输是8字节,这样struct在默认的情况上,编译器会对struct的结构进行(32位机)4的倍数或(64位机)8的倍数的数据对齐。对于32位机来说,4字节对齐能够使CPU访问速度提高,比如说一个long类型的变量,如果跨越了4字节边界存储,那么CPU要读取两次,这样效率就低了,但需要注意的是,如果在32位机中使用1字节或者2字节对齐,不仅不会提高效率,反而会使变量访问速度降低。
在默认情况下,编译器为每一个变量或数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变默认的对界条件:
1)使用伪指令#pragma pack(n),C编译器将按照n个字节对齐。
2)使用伪指令#pragma pack(),取消自定义字节对齐方式。
3)另外,还有如下的一种方式:_attribute((aligned(n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。_attribute_((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
例如如下数据结构:
struct test
{
charx1;
short x2;
float x3;
char x4;
};
由于编译器默认情况下会对struct作边界对齐,结构的第一个成员x1,其偏移地址为0,占据了第1个字节,第二个成员x2为short类型,其起始地址必须2字节对齐,因此编译器在x2和x1之间填充了一个空字节。结构的第三个成员x3和第四个成员x4恰好落在其自然边界地址上,在它们前面不需要额外的填充字节。在test结构中,成员x3要求4字节对界,是该结构所有成员中要求的最大边界单元,因而test结构的自然对界条件为4字节,编译器在成员x4后面填充了3个空字节。整个结构所占据空间为12字节。
再例如如下数据结构:
struct s1
{
short d;
int a;
short b;
}a1;
在32位机器下,short型占两个字节,int型占4个字节,所以为了满足字节对齐,变量d除了自身所占用的两个字节外,还需要再填充两个字节,变量a占用4个字节,变量b除了自身占用的两个字节,还需要两个填充字节,所以最终s1的sizeof为12。
字节对齐的细节和编译器实现相关,但一般而言,满足以下3个准则:
1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除。
2)结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍。如有需要,编译器会在成员之间加上填充字节。
3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
需要注意的是,基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型,这里所说的“数据宽度”就是指其sizeof的大小,在32位机器上,这些基本数据类型的sizeof大小分别为1、2、4、4、8。由于结构体的成员可以是复合类型,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整体。如果一个结构体中包含另外一个结构体成员,那么此时最宽基本类型成员不是该结构体成员,而是取基本类型的最宽值。但在确定复合类型成员的偏移位置时,则是将复合类型作为整体看待,即复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度,达到程序优化的目的。