位字段的意外行为

b1s*_*sub 7 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));
}
Run Code Online (Sandbox Code Playgroud)

并且输出有点出乎意料.

12
Run Code Online (Sandbox Code Playgroud)

如C11草案所述,

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

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


编辑:

将所有的_Bools 替换int为预期的作品......我不确定为什么......


编辑:

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

Lun*_*din 5

这些是位字段。“预期”输出没有太多影响,因为标准对这些输出的规定非常糟糕。此外,编译器往往对它们的支持很差。

首先,您引用的完整部分(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;
};
Run Code Online (Sandbox Code Playgroud)

现在,如果您想要确定性的、可移植的代码,您应该做的就是将位字段扔出窗口并使用按位运算符。它们是 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;
Run Code Online (Sandbox Code Playgroud)

然后设计 setter/getter 函数或宏来设置/获取此 32 位块中的数据。如果编写得当,您甚至可以使此类代码与字节顺序无关。