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

时间:2017-06-08 19:36:47

标签: c++

我有以下代码:

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进行位域会导致这种奇怪的行为?

2 个答案:

答案 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;
}