来自 C++ 中位域的掩码

Mask from bitfield in C++

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

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

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

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

像这样:

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++ 中直接检查其内存来从结构中提取各个相邻位字段。

来自Cppreference

The following properties of bit-fields are implementation-defined:

  • The value that results from assigning or initializing a signed bit-field with a value out of range, or from incrementing a signed bit-field past its range.

  • Everything about the actual allocation details of bit-fields within the class object

    • For example, on some platforms, bit-fields don't straddle bytes, on others they do
    • Also, on some platforms, bit-fields are packed left-to-right, on others right-to-left

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

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

像这样:

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},你可以安全地获取位域的值。 (当然,提取器函数必须匹配该指针后面的对象的类型。)与使用 {pointer, mask} 对相比,它的开销并不大;函数指针可能比 64 位机器上的掩码大 4 个字节(如果有的话)。提取器函数本身只是一个内存负载、一些小动作和一个 return 指令。它仍然会非常快。

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

或者,C++ 允许在具有共同初始成员的 standard-layout 结构之间进行转换。 (不过请记住,一旦继承或 private/protected 成员参与其中,这就会崩溃!上面的第一个解决方案也适用于所有这些情况。)

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

另请参阅:Can memcpy be used for type punning?