从位域获取全部价值

时间:2015-02-28 20:35:57

标签: c++ bit-shift bit-fields

我希望创建一个Block结构,用于我正在构建的体素游戏(只是背景上下文),但是我遇到了保存和加载的问题。

我可以将一个块表示为单个Uint16并移位这些位以获取不同的元素,例如blockID和health,或者我可以使用如下所示的位域:

struct Block
{
    Uint16 id : 8;
    Uint16 health : 6;
    Uint16 visible : 1;
    Uint16 structural : 1;
}

使用第一种方法,当我希望保存块数据时,我可以简单地将Uint16的值转换为十六进制值并将其写入文件。通过加载,我可以简单地读取并转换数字,然后返回通过手动位移读取各个位。

我的问题是我无法弄清楚如何使用bitfields方法获取Uint16的整个值,这意味着我无法将块数据保存为单个十六进制值。

所以,问题是如何获取存储在我的块结构中的实际单个Uint16,该结构由不同的位字段组成。如果不可能,那就没问题,因为我已经说过我的手动位移方法工作得很好。我只是想分析一下,看看哪种存储和修改数据的方法真的更快。

如果我错过了一个关键细节或者您需要帮助我的任何额外信息,请务必询问。

5 个答案:

答案 0 :(得分:1)

至少有两种方法可以满足您的需求:

  • Bit Shifting
  • 铸造

位移

您可以通过将位字段转换为uint16_t来构建uint16_t

uint16_t halfword;
struct Bit_Fields my_struct;
halfword = my_struct.id << 8;
halfword = halfword | (my_struct.health << 2);
halfword = halfword | (my_struct.visible << 1);
halfword = halfword | (my_struct.structural);

铸造

另一种方法是将结构实例强制转换为uint16_t

uint16_t halfword;
struct Bit_Fields my_struct;
halfword = (uint16_t) my_struct;

字节序

关注的一个问题是Endianness;或多字节值的字节顺序。这可能与位在16位单元内的位置起作用。

答案 1 :(得分:1)

工会可能是最干净的方式:

#include <iostream>

typedef unsigned short Uint16;

struct S {
  Uint16 id : 8;
  Uint16 health : 6;
  Uint16 visible : 1;
  Uint16 structural : 1;
};
union U {
 Uint16 asInt;
 S asStruct;
};

int main() {
  U u;
  u.asStruct.id = 0xAB;
  u.asStruct.health = 0xF;
  u.asStruct.visible = 1;
  u.asStruct.structural = 1;
  std::cout << std::hex << u.asInt << std::endl;
}

打印出cfab

<强>更新

经过进一步考虑和深入阅读后,我认为任何一种类型的惩罚都是不好的。相反,我建议只是咬住子弹并明确地做一点点来构建你的序列化值:

#include <iostream>

typedef unsigned short Uint16;

struct Block
{
  Uint16 id : 8;
  Uint16 health : 6;
  Uint16 visible : 1;
  Uint16 structural : 1;

  operator Uint16() {
    return structural | visible << 2 | health << 4 | id << 8;
  }
};

int main() {
  Block b{0xAB, 0xF, 1, 1};
  std::cout << std::hex << Uint16(b) << std::endl;
}

这还有额外的好处,它打印abf5与初始化顺序匹配。

如果您担心性能,而不是使用operator成员函数,您可以使用编译器优化的函数:

...

constexpr Uint16 serialize(const Block& b) {
  return b.structural | b.visible << 2 | b.health << 4 | b.id << 8;
}

int main() {
  Block b{0xAB, 0xF, 1, 1};
  std::cout << std::hex << serialize(b) << std::endl;
}

最后,如果速度比内存更重要,我建议删除位字段:

struct Block
{
  Uint16 id;
  Uint16 health;
  Uint16 visible;
  Uint16 structural;
};

答案 2 :(得分:1)

生活在边缘(未定义行为)..

天真的解决方案是 reinterpret_cast 对对象的基础类型的对象的引用,滥用标准的第一个非静态数据成员的事实-layout 类与对象本身位于同一地址。

struct A {         
  uint16_t         id : 8;
  uint16_t     health : 6;
  uint16_t    visible : 1;
  uint16_t structural : 1;
};

A a { 0, 0, 0, 1 };
uint16_t x = reinterpret_cast<uint16_t const&> (a);

上述内容可能看起来准确,并且经常(并非总是)会产生预期结果 - 但它会遇到两个大问题:

  • 对象中位字段的分配是实现定义的和;
  • 类类型必须是 standard-layout


没有什么可以说位字段将按照您声明的顺序存储,即使是这种情况,编译器也可能在每个位字段之间插入填充(因为这是允许)。

总结一下; 位字段如何在内存中高度实现定义,试图推断这种行为需要您查看有关此事项的实现文档。


使用union

怎么样?

建议

坚持使用 bit-fiddling 方法,除非您绝对可以证明运行代码的每个实现都按照您希望的方式处理它。


标准(N4296)说什么?

  

9.6p1 位字段 [class.bit]

     
    

[...] 类对象中位域的分配是实现定义的。位字段的对齐是实现定义的。的 [...]

  

  

9.2p20 课程 [class]

     
    

如果标准布局类对象具有任何非静态数据成员,则其地址与其第一个非静态数据成员的地址相同。的 [...]

  

答案 3 :(得分:0)

这不是位字段的良好用法(实际上,很少有)。

无法保证您的位字段的顺序与它们声明的顺序相同;它可能会在您的应用程序的构建之间发生变化。

您必须使用shift和按位或运算符手动将您的成员存储在uint16_t中。作为一般规则,在处理外部存储时,不应该只是转储或盲目复制数据;你应该手动序列化/反序列化它,以确保它符合你期望的格式。

答案 4 :(得分:0)

您可以使用union:

typedef union
{
    struct
    {
        Uint16 id : 8;
        Uint16 health : 6;
        Uint16 visible : 1;
        Uint16 structural : 1;
    } Bits;

    Uint16 Val;
} TMyStruct;