位字段的意外行为

我编译了代码,

#include  struct s { int a : 6; _Bool b : 1; _Bool c : 1; _Bool d : 1; _Bool e : 1; _Bool f : 1; _Bool g : 1; int h : 12; }; void main(void) { printf("%d\n", sizeof(struct s)); } 

输出有点出乎意料。

 12 

如C11草案所述,

…如果剩余足够的空间,紧跟在结构中另一个位域之后的位域应被打包到同一单元的相邻位中……

因为我使用了32位编译器,所以我预计它会适合4个字节。 具体来说,我使用了gcc(tdm-1)5.1.0。 这是违反标准的吗?


编辑:

将所有_Bool s替换为int按预期工作……我不确定为什么……


编辑:

在gcc 5.4.0中,代码按预期工作。 这个问题的关键点是为什么尾随的_Boolint不适合第一个 。 我想我没有做太多关于实现的假设(除了int至少4个字节,这是可以接受的), 我在这里谈论C标准的C保证行为 。 因此,我不能同意下面的一些评论。

这些是位字段。 “预期”输出的方式并不多,因为这些标准很难指定。 此外,编译器往往对它们的支持很差。

首先,您引用的完整部分(6.7.2.1/11)说:

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

所有这些意味着你几乎不能假设这些位如何在内存中结束。 你不可能知道编译器将如何排列对齐,你不可能知道位的位顺序,你无法知道签名,你无法知道endianess。

至于if int_Bool将合并到同一个“存储单元”……好吧,为什么会这样? 它们是不兼容的类型。 C标准没有提到当你有两个不相容类型的相邻比特字段时会发生什么 – 它对主观解释持开放态度。 我怀疑会有各种类型的别名问题。

因此,编译器完全可以将所有这些放在单独的“存储单元”中。 如果您相邻地放置相同类型的项目,我会希望它合并它们或引用的部分没有任何意义。 例如,我希望从以下大小8:

 struct s { int a : 6; int h : 12; _Bool b : 1; _Bool c : 1; _Bool d : 1; _Bool e : 1; _Bool f : 1; _Bool g : 1; }; 

现在,如果你想要确定性的行为,你应该做什么,可移植代码是将位字段抛出窗口并使用逐位运算符。 它们具有100%的确定性和便携性。

假设你实际上不想要一些神秘的签名号码字段,原始代码提示,那么:

 #define a UINT32_C(0xFC000000) #define b (1u << 18) #define c (1u << 17) #define d (1u << 16) #define e (1u << 15) #define f (1u << 14) #define g (1u << 13) #define h UINT32_C(0x00000FFF) typedef uint32_t special_thing; 

然后设计setter / getter函数或宏来设置/获取这个32位块的数据。 写得正确,你甚至可以使这样的代码与endianess无关。

我的猜测是编译器将int位字段和_Bool位字段视为不同的类型。 因此它将所有_Bool位域和int位域组合在一起,但不组合_Boolint位域。 所以结构将这样组合成12个字节:

 struct s { int a : 6; // no more int bit-fields, so 4 bytes (usual size) // 6 bits fits in one byte, but these might have to be aligned // on a 4 byte boundary for efficient access, so 4 bytes in total _Bool b : 1; // combine _Bool c : 1; // combine _Bool d : 1; // combine _Bool e : 1; // combine _Bool f : 1; // combine _Bool g : 1; // combine with above _Bool bit-fields to make 6 bits int h : 12; // no more int bit-fields around again, so 4 bytes }; 

编辑:2011 C标准中的这一段可以解释为_Bool位字段的行为不同:

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

如果_Bool位字段具有_Bool的语义,那么实现者可能会解释说“像_Bool b : 1;那样_Bool b : 1;等同于_Bool b;

这里的快速测试似乎证实了这一理论。