我有以下代码:
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 %x\n", 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进行位域会导致这种奇怪的行为?
答案 0 :(得分:5)
编译器可能会在结构成员之间添加填充(或者它可能不会自行决定),当您尝试以一堆位来访问整个结构时,您不会考虑这一点。基本上你不能这样做(行为是未定义的,你的代码只是错误)。您需要按名称或访问结构成员,使用特定于编译器的扩展来控制布局/填充。
答案 1 :(得分:3)
位域的存储器布局是实现定义的。例如,提供此online draft c++ standard:
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 %x\n", myView.foo.a, myView.foo.b, myView.foo.c);
// Output: e a d
return 0;
}