首先,这是 ISO C 标准对位字段的说明,引用了 2011 ISO C 标准的N1570草案,第 6.7.2.1 节:
位域的类型应为 、 、 或其他一些实现定义的类型的合格或非
_Bool
合格signed int
版本unsigned int
。是否允许原子类型是实现定义的。
...
位域被解释为具有由指定位数组成的有符号或无符号整数类型。如果值 0 或 1 存储到类型为 的非零宽度位域中
_Bool
,则位域的值应与存储的值进行比较;_Bool
位域具有 a 的语义_Bool
。实现可以分配任何大到足以容纳位域的可寻址存储单元。如果有足够的空间剩余,紧跟在结构中另一个位域之后的位域将被打包到同一单元的相邻位中。如果剩余空间不足,则将不适合的位域放入下一个单元还是与相邻单元重叠是实现定义的。单元内位域的分配顺序(高位到低位或低位到高位)是实现定义的。未指定可寻址存储单元的对齐方式。
对于任何struct
类型,该类型的对齐至少是该类型任何成员的最大对齐,任何类型的大小是其对齐的倍数。例如,如果一个结构包含一个(非位域)int
成员,并且int
需要 4 字节对齐,那么结构本身需要 4 字节或更多对齐。
_Bool
许多编译器允许除和类型之外的整数类型的位字段int
。
对于至少一些编译器,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 的编译器看到了类似的行为。
实验表明,定义为 as_Bool
或 as的多个位域unsigned
可以打包到单个字节内的相邻位中(实际上这是必需的),因此位域本身没有严格的对齐要求。
我知道结构成员的布局主要是实现定义的,我不认为 gcc 的行为违反了 C 标准。
所以我的问题(终于!)是,为什么 gcc(连同至少一个不相关的 C 编译器,可能还有更多)这样做?gcc 的作者是否假设位字段的声明类型必须影响包含结构的大小和对齐方式?他们在这个假设中正确吗?我错过了 C 标准本身的要求吗?
这是一个展示该行为的测试程序。如果你想在你的系统上运行它,你可能需要注释掉它的一部分,如果你使用的是不支持某些新功能的旧编译器,或者不允许某些类型的位字段。我很想知道是否有编译器的行为不像gcc。
#include <stdio.h>
#include <limits.h>
#include <stdint.h>
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)