繁体   English   中英

来自 C++ 中位域的掩码

[英]Mask from bitfield in C++

这是一个我找不到好的答案的小谜题:

给定一个带有位域的结构,例如

struct A {
    unsigned foo:13;
    unsigned bar:19;
};

C++ 中是否有一种(可移植的)方法来为其中一个位域获取正确的掩码,最好是作为编译时常量 function 或模板?

像这样:

constinit unsigned mask = getmask<A::bar>();  // mask should be 0xFFFFE000

理论上,在运行时,我可以粗略地做:

unsigned getmask_bar() {
    union AA {
        unsigned mask;
        A fields;
    } aa{};
    aa.fields.bar -= 1;
    return aa.mask;
}

甚至可以将其包装在宏中(讨厌。)以使其“通用”。

但我想您可以很容易地看出这种方法的各种不足之处。

有更好的通用 C++ 方法吗? 或者甚至是一种不太好的方式? 下一个 C++ 标准有什么有用的东西吗? 反射?

编辑:让我补充一点,我正在尝试找到一种使位域操作更加灵活的方法,以便程序员可以使用掩码同时修改多个字段。 我追求简洁的符号,这样就可以在没有大量样板的情况下简洁地表达事物。 将在 I/O 驱动程序中使用硬件寄存器作为一个用例。

不幸的是,没有更好的方法 - 事实上,没有办法通过直接在 C++ 中检查其 memory 从结构中提取单个相邻位字段。

来自Cppreference

位域的以下属性是实现定义的:

  • 通过使用超出范围的值分配或初始化带符号的位域,或将带符号的位域递增超过其范围而产生的值。

  • class object 中位域的实际分配细节

    • 例如,在某些平台上,位域不跨越字节,而在其他平台上
    • 此外,在某些平台上,位字段是从左到右打包的,在其他平台上是从右到左打包的

你的编译器可能会给你更强的保证; 但是,如果您确实依赖于特定编译器的行为,则不能指望您的代码可以与不同的编译器/体系结构对一起工作。 据我所知,GCC 甚至没有记录它们的位字段打包,而且它因架构而异。 因此,您的代码可能适用于 x86-64 上特定版本的 GCC,但实际上会破坏其他所有版本,包括同一编译器的其他版本。

如果您真的希望能够以通用方式从随机结构中提取位域,最好的办法是传递一个 function 指针(而不是掩码); 这样,function 就可以安全地访问该字段并将值返回给它的调用者(或设置一个值)。

像这样:

template<typename T>
auto extractThatBitField(const void *ptr) {
  return static_cast<const T *>(ptr)->m_thatBitField;
}

auto *extractor1 = &extractThatBitField<Type1>;
auto *extractor2 = &extractThatBitField<Type2>;
/* ... */

现在,如果你有一对{pointer, extractor} ,你可以安全地获取位域的值。 (当然,提取器 function 必须匹配该指针后面的 object 的类型。)与使用{pointer, mask}对相比,开销并不大; function 指针可能比 64 位机器上的掩码大 4 个字节(如果有的话)。 提取器 function 本身将只是一个 memory 加载、一些微调和一个返回指令。 它仍然会非常快。

这是可移植的,并受 C++ 标准支持,这与直接检查位域的位不同。

或者,C++ 允许在具有共同初始成员的标准布局结构之间进行转换。 (尽管请记住,一旦 inheritance 或私人/受保护成员参与其中,这就会崩溃,上面的第一个解决方案也适用于所有这些情况。)

struct Common {
  int m_a : 13;
  int m_b : 19;
  int : 0; //Needed to ensure the bit fields end on a byte boundary
};

struct Type1 {
  int m_a : 13;
  int m_b : 19;
  int : 0;
  
  Whatever m_whatever;
};

struct Type2 {
  int m_a : 13;
  int m_b : 19;
  int : 0;
  
  Something m_something;
};

int getFieldA(const void *ptr) {
  //We still can't do type punning directly due
  //to weirdness in various compilers' aliasing resolution.
  //std::memcpy is the official way to do type punning.
  //This won't compile to an actual memcpy call.
  Common tmp;
  std::memcpy(&tmp, ptr, sizeof(Common));
  return tmp.m_a;
}

另请参阅: memcpy 可以用于类型双关吗?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM