结构体嵌套的内存对齐问题:嵌套与扁平结构体布局是否一致?
问题描述
我在自己机器上运行附带的程序,输出是1、2、3、4。这种行为是普遍预期的吗?下面两种结构体的内存填充(padding)会不会因为嵌套特性而存在差异?
嵌套结构体
struct { Vector position; Vector velocity; Vector acceleration; };
扁平结构体
struct { double x; double y; double dxdt; double dydt; };
一般来说,所有成员都是同一类型Type的结构体:
struct { Type member1; Type member2; ... Type memberN; }
和下面这种分组嵌套的结构体,内存布局是否完全相同?
struct { struct group1 { Type member1; Type member2; ... Type memberI}; }; struct group2 { Type memberIplus1; Type memberIplus2; ... Type memberIplusJ}; }; ... struct groupM { ... Type memberN }; }
测试程序
#include <stdio.h> typedef struct vector { double x; double y; } Vector; typedef union { struct { Vector position; Vector velocity; Vector acceleration; }; struct { double x; double y; double dxdt; double dydt; }; } State; int main() { State state = { .position = {1,2}, .velocity = {3,4}, }; printf("%0.f\n",state.x); printf("%0.f\n",state.y); printf("%0.f\n",state.dxdt); printf("%0.f\n",state.dydt); return 1; }
解答
你机器上输出1、2、3、4的情况,在大多数常见编译器(比如GCC、Clang、MSVC)的默认设置下是符合预期的,但这并不是C标准强制要求的行为。
嵌套与扁平结构体的填充差异
针对你给出的示例:Vector是包含两个double的结构体,它本身的大小是16字节(两个8字节的double,内部无填充),所以嵌套结构体里的position、velocity会连续排布,和扁平结构体里四个double的连续排布内存完全重合,此时两者填充无差异。
但如果嵌套的子结构体本身因为对齐要求带有末尾填充,情况就会不同:比如假设Type是4字节的int,某个子结构体包含3个int,为了满足8字节对齐要求,编译器会给这个子结构体额外加4字节填充,此时嵌套版本的总大小会比扁平版本大,内存布局自然出现差异。
通用情况的内存布局一致性
当所有成员都是同一类型Type时,只有当每个嵌套的子结构体内部没有额外填充,且子结构体大小是Type大小的整数倍时,嵌套版本和扁平版本的内存布局才会完全一致:
- 如果子结构体的成员数量符合对齐要求,没有末尾填充,嵌套后成员会连续排布,和扁平版本一致;
- 如果子结构体有末尾填充,嵌套版本会在每个子结构体后带上填充字节,这就和扁平版本的连续成员布局产生差异。
关于你的测试程序
你的程序用了匿名union让两个结构体共享内存,主流编译器默认会让匿名结构体的成员直接布局在union的起始地址,所以position.x对应x、position.y对应y、velocity.x对应dxdt、velocity.y对应dydt,才输出了1、2、3、4。但这种匿名结构体/union的布局属于编译器扩展,C标准并没有明确规定,所以在小众编译器或特殊编译选项下,结果可能不同。
内容的提问来源于stack exchange,提问作者Angelos

