C 为什么位字段的类型会影响包含结构的大小?
首先,引用2011年ISO C标准草案第6.7.2.1节,以下是ISO C标准关于位字段的说明: 位字段的类型应为合格或不合格C 为什么位字段的类型会影响包含结构的大小?,c,gcc,struct,C,Gcc,Struct,首先,引用2011年ISO C标准草案第6.7.2.1节,以下是ISO C标准关于位字段的说明: 位字段的类型应为合格或不合格 \u Bool、有符号整数、无符号整数或其他版本 实现定义的类型。它是由实现定义的 允许使用原子类型 位字段被解释为具有有符号或无符号整数类型 由指定数量的位组成。如果值0或1为 存储在类型为\u Bool的非零宽度位字段中 位字段应与存储的值进行比较;a\u Bool 位字段具有\u Bool的语义 一个实现可以分配任何大的可寻址存储单元 足够容纳一个位域。如果剩余
\u Bool
、有符号整数
、无符号整数
或其他版本
实现定义的类型。它是由实现定义的
允许使用原子类型
位字段被解释为具有有符号或无符号整数类型
由指定数量的位组成。如果值0或1为
存储在类型为\u Bool
的非零宽度位字段中
位字段应与存储的值进行比较;a\u Bool
位字段具有\u Bool
的语义
一个实现可以分配任何大的可寻址存储单元
足够容纳一个位域。如果剩余的空间足够大,则需要一个位字段
结构中紧跟其后的另一个位字段应打包
插入同一单元的相邻位。如果空间不足,
是否将不适合的位字段放入下一个单元或
实现定义了相邻单元的重叠。秩序
单元内位字段的分配(从高阶到低阶或
低阶到高阶)是实现定义的。路线
未指定可寻址存储单元
对于任何struct
类型,该类型的对齐方式至少是该类型任何成员的最大对齐方式,并且任何类型的大小都是其对齐方式的倍数。例如,如果结构包含(非位字段)int
成员,并且int
需要4字节对齐,则结构本身需要4字节或更多字节对齐
许多编译器允许整数类型的位字段,而不是\u Bool
和int
类型
至少对于某些编译器,包含位字段的结构的对齐方式至少是位字段声明类型的对齐方式。例如,对于x86_64上的gcc 4.7.2,给定:
struct sb {
_Bool bf:1;
};
struct si {
unsigned bf:1;
};
gcc给出了struct sb
1字节的大小和对齐方式(即\u Bool
的大小和对齐方式),以及struct si
4字节的大小和对齐方式(即int
的大小和对齐方式)。它对实现定义类型的位字段执行相同的操作;定义为long bf:1的位字段代码>强制封闭结构的大小和对齐方式为8字节。即使在这两种情况下,位字段bf
是宽度仅为1位的对象,也可以执行此操作
我在SPARC/Solaris 9上见过Sun编译器的类似行为
实验表明,定义为\u Bool
或无符号
的多个位字段可以打包到单个字节内的相邻位中(事实上这是必需的),因此位字段本身没有严格的对齐要求
我知道结构成员的布局在很大程度上是由实现定义的,我不认为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;
}
在某种程度上,您已经用标准中的这句话回答了这个问题:
未指定可寻址存储单元的对齐方式
编译器可以选择任何对齐方式并遵循C标准,但这还不是全部
为了使使用不同编译器编译的代码能够互操作,平台ABI必须指定这些详细信息。例如,Linux x86使用的SYS-V i386 ABI表示:
位字段遵循相同的大小
以及与其他结构和联合成员一样的对齐规则,包括
增补:[……]
- 位字段必须完全驻留在与其声明的位字段相适应的存储单元中
类型
因此,无论宽度如何,长
位字段必须位于与4字节边界对齐的某个位置。受评论中讨论的启发。clang(至少是我当前安装在32位框上的一个)表示结构s64的32位,这里我的gcc也是这么说的。@DanielFischer clang 3.0、icc 13.1.1、tcc 0.9.25和gcc 4.7产生了基思发布的精确输出。(在amd64盒上)@cnicutar并不让我感到惊讶,正如我所说,我现在在32位盒上,32位是这里的最大对齐要求。我认为基本原理是将指定类型解释为对齐请求。因此,您可以获得所请求的对齐方式,或者如果需要更多对齐方式,则可以获得最大对齐方式。如果使用-Wpadded
编译,您将获得一些警告:将结构大小填充到对齐边界[-Wpadded]
(clang还表示添加了多少填充)。这似乎证实了编译器将指定的类型作为对齐提示。可能假设程序员通过声明一个大于必需的类型知道他在做什么。好吧,我不会说我自己已经回答了这个问题。在任何情况下,这都会引发另一个问题:ABI中该要求的理由是什么?我想它必须执行一些规则,但为什么要这样呢?(我几乎可以对“它声明的类型”的含义提出质疑;t
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)