C/C++:uint8_t位域的行为不正确且不一致



我有以下代码:

struct Foo {
uint16_t a:5;
uint16_t b:5;
uint16_t c:5;
};
uint16_t bits = 0xdae;
const Foo& foo = *reinterpret_cast<const Foo*>(&bits);
printf("%x %x %xn", foo.a, foo.b, foo.c);

输出是我在纸上计算时所期望的:

E D 3

如果我使用 uint32_t 而不是 uint16_t 位域,我会得到相同的结果。

但是当我使用 uint8_t 位域而不是 uint16_t 时,我得到的结果不一致,即:

E d 6

E d 16

两者都不正确。为什么将uint8_t用于位域会导致这种奇怪的行为?

编译器可能会在结构成员之间添加填充(或者它可能不会,由其判断),当您只是尝试将整个结构作为一堆位访问时,您不会考虑这一点。基本上你不能只是这样做(行为是未定义的,你的代码只是有问题)。您需要按名称访问结构成员或使用特定于编译器的扩展来控制布局/填充。

位字段的内存布局是实现定义的。例如,授予以下在线 c++ 标准草案:

9.6 位字段

(1) ...类对象中位字段的分配为 实现定义。位字段的对齐方式为 实现定义。

因此,由于您不会通过长度为 0 的位字段引入显式填充,因此您无法控制编译器在内存中如何布局结构。实际上,我认为您会产生未定义的行为,因为您将一个指针转换为另一个指针,可能具有不同的对齐要求,并且访问填充位(就像分配重新解释的强制转换时发生的那样)也是未定义的行为。

请注意,您可以通过标准化方式控制位字段的填充:

(2) 省略标识符的位字段声明声明 未命名的位域。未命名的位字段不是成员,不能 初始 化。[注意:未命名的位字段对于填充到 符合外部强加的布局。— 尾注 ] 作为特例, 宽度为零的未命名位字段指定 分配单元边界处的下一个位字段。仅当声明 未命名的位字段 常量表达式的值可能等于 零。

请参阅以下使用此功能的代码;请注意,它会产生不同的结果,因为我发现无法控制填充,以便我可以获得连续的 5 位馅饼。因此,我将示例改编为适用于字节边框级别的内容:

struct Foo {
uint8_t a:5;
uint8_t :0;
uint8_t b:5;
uint8_t :0;
uint8_t c:5;
};
union DifferentView {
uint32_t bits;
struct Foo foo;
};
int main()
{
union DifferentView myView;
myView.bits = 0x0d0a0e;
printf("%x %x %xn", myView.foo.a, myView.foo.b, myView.foo.c);
// Output: e a d
return 0;
}

最新更新