深度解析结构体类型地址偏移量相关问题
Q1:什么是偏移量?
A:结构体变量中成员的地址和结构体变量地址之差。可以用offsetof(type, member)宏来确定成员的实际位置(定义于stddef.h),其中type是结构体类型,member是成员名。可知:第一个成员的偏移量为0,最后一个成员的偏移量加上最后一个成员的大小却不一定等于结构体大小(先卖个关子=。。=)。
Q2:什么是地址对齐?
A:结构体成员是按照声明的顺序存储在连续的内存空间中,而结构体成员的类型不一定相同,系统在存储结构体时不是像数组一样将各个成员大小简单相加,而需要考虑地址对齐问题。
先来简单看一个例子:
Q3:上述两种形式的结构体大小是否相同?
A:不相等,虽然结构体成员相同,但是由于声明顺序不同导致地址偏移量不同。具体而言第一个结构体大小为16,第二个为24。
Q4:结构体存储时地址对齐,编译器需要遵循什么原则?
A:两条原则:
1.结构体变量中成员的偏移量必须是成员大小的整数倍;
2.结构体大小必须是所有成员大小的整数倍,即所有成员大小的公倍数。
对照上述两条原则,回过来看上述两个结构体大小是不是清楚明白了?Student1很好理解,Student2中scores成员偏移量为16,加上自身大小4后为20,由于20不是8的整数倍,去最小公倍数24为整个结构体大小。(最后一个成员的偏移量加上最后一个成员的大小却不一定等于结构体大小=。。=)
-------------------------------------------我是一条可爱的分割线-------------------------------------------------------
增加点难度,再来看一个例子:
大家思考一下结构体Student3的大小?结构体ss偏移量?结构体ss大小为8,如果将ss当作一个整体Student3大小是不是24?
测试结果却是:
Q5:出现结构体嵌套时,怎么计算结构体大小?
A:上述两个原则需要修改一下:
1.展开后的结构体的第一个成员的偏移量(嵌套的结构体偏移量)应当是被展开的结构体中最大成员的整数倍;
2.结构体大小必须是所有成员大小的整数倍,这里计算的是展开后的成员,而不是将嵌套的结构体看作一个整体。
总结与反思:了解结构体存储方式后,我们该怎么做才能充分利用内存,减少边界对齐带来的空间损失?
1.重排结构体成员声明列表,让那些对边界要求最严格(大)的成员首先出现,对边界要求最弱(小)的成员最后出现;
2.在考虑程序的可读性和可维护性的前提下,可以把相关联的成员放在一起而不用考虑列表顺序问题,否则参考1;
3.在程序声明几百个甚至几千个结构体时,减少内存的浪费要比程序的可读性更为急迫,这时可以通过增加注释来提高可读性。
tip:如何给结构体变量分配空间由编译器决定,以上代码均在linux/GCC下编译,windows/VC也是如此,其他平台可能有不同的处理方式。