5.1.5 结构体变量的sizeof
一般来说,结构体变量占据的内存大小是所有成员变量占据内存大小之和,但有些例外需要特别注意,先看一个结构体。
struct ExS1 { char c1; short s1; int i1; };
由于char偏移量必须为1的倍数,int型必须为4的倍数,float偏移量必须为4,double偏移量必须为8,short偏移量必须为2。从字面上来看,sizeof(ExS1)的结果似乎应该是1+2+4=7,但实际上,返回结果为8,这涉及字节对齐机制。
字节对齐的细节和编译器实现相关,一般而言有以下3个准则。
(1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除。
(2)结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(Internal Adding)。
(3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(Trailing Padding)。
字节对齐有助于加快计算机的取数速度,节省指令周期。对示例结构ExS1来说,根据准则(2),每个数据成员相对于首地址的偏移量是成员大小的整数倍,需要在char型成员c1和short型成员s1之间加入一个空字节,所以整个结构的长度变为8。
变化一下ExS1定义的顺序,会有以下结果。
struct ExS1 { short s1;//存放在[0]……[1], int i1;//根据第(2)个准则,偏移必须是4的整数,前面填充2~3,存放在[4]……[7] char c1;//存放在[8],但根据第(3)个准则,所以整个结构必须是4的整数倍,填充9~11 };
sizeof(ExS1)的结果变为12,只是交换了下数据顺序,为什么类结构变量的大小又扩大了呢?这还是由上述3个准则决定的,根据准则(2),需要在s1和i1之间加入两个空字节,根据准则(3),需要在c1后加入3个空字节,最后,类变量的大小如下所示。
2+2(插入的空字节)+4+1+3(插入的空字节)=12
说明
成员变量的定义顺序也会影响结构体变量占据的内存大小。
注意
3个字节对齐准则中反复提到了“基本数据类型”的概念,所谓基本类型是指像char、short、int、float及double这样的内置数据类型。数据宽度是指其sizeof的大小。一方面,由于结构体可以嵌套,其成员可以是复合类型,比如另外一个结构体即后面要介绍的共用体及类对象,在寻找最宽基本类型成员时,不是把复合成员看成是一个整体,而是考虑复合类型成员的子成员,另一方面,类内复合成员的内存分配仍要遵守3个准则。来看以下示例。
struct A { char c1; short s1; int i1; }; struct B { char B_c1; A B_a; char B_c2; };
前面的分析已经指出,A的大小为8,在考虑B的最宽简单类型成员时,要将类型为A的复合成员B_a“打散”,所以结构B的最宽简单类型为int,大小为4个字节。根据准则(3)可知,sizeof(B)的值也应该被4整除。准则(2)应解释为“复合成员相对于结构体首地址的偏移量(offset)都是复合成员中最宽简单类型成员大小的整数倍,”,因此,B_a的偏移量应为4的倍数,B_c1和B_a之间插入3个空字节,加上B_c2共计13个字节,13是不能被4整除的,末尾还得补上3个填充字节,最后得到sizeof(B)的值为16。
注意
C++结构除了成员变量外,还可以有成员函数,关于这方面的内容将在第8章中进行介绍。