7

首先,这是 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 sb1 字节的大小和对齐方式(即 的大小和对齐方式_Bool),以及struct si4 字节的大小和对齐方式(即 的大小和对齐方式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)
4

1 回答 1

6

在某种程度上,你已经用标准中的这个引文自己回答了这个问题:

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

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

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

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

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

然后,无论宽度如何,long位域都必须位于与 4 字节边界对齐的内容中。

于 2013-04-12T21:47:36.187 回答