为什么位域的类型会影响包含结构的大小?

首先,这是ISO C标准关于位域的内容,引用2011年ISO C标准的N1570草案,第6.7.2.1节:

位字段的类型应为_Boolsigned intunsigned int或其他实现定义类型的限定或非限定版本。 它是实现定义的,是否允许primefaces类型。

位字段被解释为具有由指定位数组成的有符号或无符号整数类型。 如果将值0或1存储到_Bool类型的非零宽度位字段中,则位字段的值应等于存储的值; _Bool位字段具有_Bool的语义。

实现可以分配足够大的任何可寻址存储单元来保持位字段。 如果剩余足够的空间,则紧跟在结构中的另一个位字段之后的位字段将被打包到相同单元的相邻位中。 如果剩余的空间不足,则是否将不适合的位域放入下一个单元或重叠相邻单元是实现定义的。 单元内的位域分配顺序(高阶到低阶或低阶到高阶)是实现定义的。 未指定可寻址存储单元的对齐。

对于任何struct类型,类型的对齐方式至少是该类型的任何成员的最大对齐方式,并且任何类型的大小都是其对齐方式的倍数。 例如,如果结构包含(非位域) int成员,并且int需要4字节对齐,则结构本身需要4字节对齐或更多。

许多编译器允许除_Boolint类型之外的整数类型的位字段。

对于至少一些编译器,包含位字段的struct的对齐至少是声明的位字段类型的对齐。 例如,对于x86_64上的gcc 4.7.2,给出:

 struct sb { _Bool bf:1; }; struct si { unsigned bf:1; }; 

gcc给struct sb一个大小和1个字节的对齐(这是_Bool的大小和对齐), struct si是一个大小和4个字节的对齐(这是int的大小和对齐)。 它对实现定义类型的位字段做了同样的事情; 位字段定义为long long bf:1; 强制封闭结构的8字节大小和对齐。 即使在两种情况下,位字段bf是宽度仅为1位的对象,也可以完成此操作。

我在SPARC / Solaris 9上看到了与Sun编译器类似的行为。

实验表明,定义为_Boolunsigned多个位字段可以打包到单个字节内的相邻位(实际上是必需的),因此位字段本身没有严格的对齐要求。

我理解结构成员的布局主要是实现定义的,我不相信gcc的行为违反了C标准。

所以我的问题( 最后! )是,为什么gcc(以及至少一个不相关的C编译器,可能还有更多)这样做? gcc的作者是否认为声明的位字段类型必须影响包含结构的大小和对齐? 它们在这个假设中是否正确? 我错过了C标准本身的要求吗?

这是一个展示行为的测试程序。 如果你想在你的系统上运行它,你可能需要注释掉它的一部分,如果你使用的是不支持某些较新function的旧编译器,或者不允许某些类型的位领域。 我有兴趣知道是否有编译器不像gcc那样表现。

 #include  #include  #include  int main(void) { struct sb { _Bool bf:1; }; struct s8 { uint8_t bf:1; }; struct s16 { uint16_t bf:1; }; struct s32 { uint32_t bf:1; }; struct s64 { uint64_t bf:1; }; printf("sizeof (struct sb) = %2zu (%2zu bits)\n", sizeof (struct sb), sizeof (struct sb) * CHAR_BIT); printf("sizeof (struct s8) = %2zu (%2zu bits)\n", sizeof (struct s8), sizeof (struct s8) * CHAR_BIT); printf("sizeof (struct s16) = %2zu (%2zu bits)\n", sizeof (struct s16), sizeof (struct s16) * CHAR_BIT); printf("sizeof (struct s32) = %2zu (%2zu bits)\n", sizeof (struct s32), sizeof (struct s32) * CHAR_BIT); printf("sizeof (struct s64) = %2zu (%2zu bits)\n", sizeof (struct s64), sizeof (struct s64) * CHAR_BIT); return 0; } 

这是我在我的系统上得到的输出:

 sizeof (struct sb) = 1 ( 8 bits) sizeof (struct s8) = 1 ( 8 bits) sizeof (struct s16) = 2 (16 bits) sizeof (struct s32) = 4 (32 bits) sizeof (struct s64) = 8 (64 bits) 

在某种程度上,您已使用标准中的引用自行回答了问题:

未指定可寻址存储单元的对齐。

编译器可以选择任何对齐并遵守C标准,但这不是全部。

为了使用不同编译器编译的代码进行互操作,平台ABI必须指定这些细节。 例如,Linux x86使用的SYS-V i386 ABI说:

位字段遵循与其他结构和联合成员相同的大小和对齐规则,并添加以下内容:[…]

  • 位字段必须完全驻留在适合其声明类型的存储单元中。

然后,无论宽度如何, long位域必须驻留在4字节边界上对齐的内容中。