如何简便计算结构体数据成员大小总和(含成员间填充不含结构体间填充)
嘿,这个问题问到点子上了!很多人会混淆sizeof(结构体)和结构体成员(含内部填充)的总大小——前者会包含结构体整体对齐所需的末尾填充,而你要的是排除这部分的总和。咱们分两种情况来聊:
如果你不想手动逐个成员计算(还要算填充),最靠谱的方法是利用最后一个成员的末尾偏移来计算:
- 用
offsetof宏获取最后一个成员的起始偏移,再加上该成员的大小,就能得到从结构体起始到最后一个成员末尾的总字节数(这就是你要的包含内部填充、不含结构体整体对齐填充的总和)。
举个例子,假设结构体是typedef struct { int a; char b; long c; } Foo;,那计算方式就是:
#include <stddef.h> // 必须包含offsetof的头文件 printf("%zu\n", offsetof(Foo, c) + sizeof(Foo.c));
这个方法比取实例地址的方式更简洁,而且不需要创建结构体实例。
当然,如果你连最后一个成员的名字都不想写(比如结构体成员很多或者动态生成的),那遗憾的是,C语言没有标准的方法能自动做到——因为结构体的成员布局是编译器私有的信息,没有标准接口可以遍历成员。不过某些编译器有非标准扩展(比如GCC的相关属性工具,但实用性不高),这种情况还是建议手动确认最后一个成员。
先看你的结构体定义:
#pragma pack(4) alignas(16) struct { char c[4]; short s; unsigned char u[3]; } MyStruct;
这里alignas(16)是指定结构体整体的对齐要求(结构体的大小必须是16的倍数,所以sizeof(MyStruct)会是16),但它不影响内部成员的填充布局——内部填充由#pragma pack(4)和成员自身的对齐规则决定。
你给出的&MyStruct.u[3] - &MyStruct.c[0]确实能得到正确结果(输出11),但需要写成员的具体位置。更简便的写法是用offsetof:
#include <stddef.h> printf("%zu\n", offsetof(typeof(MyStruct), u) + sizeof(MyStruct.u));
这里typeof(MyStruct)是因为你定义的是一个变量MyStruct而不是类型(如果是typedef struct { ... } MyStruct;的话,直接写MyStruct就行),这样就不用手动取地址,代码更干净。
为什么结果是11?咱们拆解一下内部布局:
char c[4]:占4字节,无填充(下一个成员short s的对齐要求是2,#pragma pack(4)取较小的2,4是2的倍数,所以直接接在后面)short s:占2字节,此时总长度到6字节;由于#pragma pack(4)的约束,成员的对齐边界不能超过4,unsigned char u[3]的对齐要求是1,但为了满足整体成员布局的对齐规则,s和u之间会被填充2字节(让u的起始偏移到8,是4的倍数),再加上u的3字节,最终总长度为4+2+2+3=11,和你的表达式输出一致。
最后,如果你想彻底避开写成员名称,那真的没有标准方法——毕竟C语言是静态类型语言,编译时才确定结构体布局,运行时没有办法动态遍历成员。
内容的提问来源于stack exchange,提问作者George Robinson

