位字段的意外行为

时间:2017-05-02 10:00:58

标签: c

我编译了代码,

#include <stdio.h>

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替换为int按预期工作......我不确定原因......

修改

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

2 个答案:

答案 0 :(得分:3)

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

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

  

实现可以分配任何可寻址的存储单元   足以容纳一个位域。如果剩余足够的空间,那就是一个位域   紧接着结构中的另一个位字段应该被打包   进入同一单元的相邻位。如果剩余空间不足,   是否将不适合的位域放入下一个单元或   重叠相邻单元是实现定义的。的顺序   单位内的位域分配(高位到低位或   低阶到高阶)是实现定义的。对齐   可寻址存储单元未指定。

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

至于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无关。

答案 1 :(得分:0)

我的猜测是编译器将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;

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