简体   繁体   English

来自 C++ 中位域的掩码

[英]Mask from bitfield in C++

Here's a little puzzle I couldn't find a good answer for:这是一个我找不到好的答案的小谜题:

Given a struct with bitfields, such as给定一个带有位域的结构,例如

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

Is there a (portable) way in C++ to get the correct mask for one of the bitfields, preferably as a compile-time constant function or template? C++ 中是否有一种(可移植的)方法来为其中一个位域获取正确的掩码,最好是作为编译时常量 function 或模板?

Something like this:像这样:

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

In theory, at runtime, I could crudely do:理论上,在运行时,我可以粗略地做:

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

That could even be wrapped in a macro (yuck.) to make it "generic".甚至可以将其包装在宏中(讨厌。)以使其“通用”。

But I guess you can readily see the various deficiencies of this method.但我想您可以很容易地看出这种方法的各种不足之处。

Is there a nicer, generic C++ way of doing it?有更好的通用 C++ 方法吗? Or even a not-so-nice way?或者甚至是一种不太好的方式? Is there something useful coming up for the next C++ standard(s)?下一个 C++ 标准有什么有用的东西吗? Reflection?反射?

Edit: Let me add that I am trying to find a way of making bitfield manipulation more flexible, so that it is up to the programmer to modify multiple fields at the same time using masking.编辑:让我补充一点,我正在尝试找到一种使位域操作更加灵活的方法,以便程序员可以使用掩码同时修改多个字段。 I am after terse notation, so that things can be expressed concisely without lots of boilerplate.我追求简洁的符号,这样就可以在没有大量样板的情况下简洁地表达事物。 Think working with hardware registers in I/O drivers as a use case.将在 I/O 驱动程序中使用硬件寄存器作为一个用例。

Unfortunately, there is no better way - in fact, there is no way to extract individual adjacent bit fields from a struct by inspecting its memory directly in C++.不幸的是,没有更好的方法 - 事实上,没有办法通过直接在 C++ 中检查其 memory 从结构中提取单个相邻位字段。

From Cppreference :来自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 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此外,在某些平台上,位字段是从左到右打包的,在其他平台上是从右到左打包的

Your compiler might give you stronger guarantees;你的编译器可能会给你更强的保证; however, if you do rely on the behavior of a specific compiler, you can't expect your code to work with a different compiler/architecture pair.但是,如果您确实依赖于特定编译器的行为,则不能指望您的代码可以与不同的编译器/体系结构对一起工作。 GCC doesn't even document their bit field packing, as far as I can tell, and it differs from one architecture to the next.据我所知,GCC 甚至没有记录它们的位字段打包,而且它因架构而异。 So your code might work on a specific version of GCC on x86-64 but break on literally everything else, including other versions of the same compiler.因此,您的代码可能适用于 x86-64 上特定版本的 GCC,但实际上会破坏其他所有版本,包括同一编译器的其他版本。

If you really want to be able to extract bitfields from a random structure in a generic way, your best bet is to pass a function pointer around (instead of a mask);如果您真的希望能够以通用方式从随机结构中提取位域,最好的办法是传递一个 function 指针(而不是掩码); that way, the function can access the field in a safe way and return the value to its caller (or set a value instead).这样,function 就可以安全地访问该字段并将值返回给它的调用者(或设置一个值)。

Something like this:像这样:

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

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

Now, if you have a pair of {pointer, extractor} , you can get the value of the bitfield safely.现在,如果你有一对{pointer, extractor} ,你可以安全地获取位域的值。 (Of course, the extractor function has to match the type of the object behind that pointer.) It's not much overhead compared to having a {pointer, mask} pair instead; (当然,提取器 function 必须匹配该指针后面的 object 的类型。)与使用{pointer, mask}对相比,开销并不大; the function pointer is maybe 4 bytes larger than the mask on a 64-bit machine (if at all). function 指针可能比 64 位机器上的掩码大 4 个字节(如果有的话)。 The extractor function itself will just be a memory load, some bit twiddling, and a return instruction.提取器 function 本身将只是一个 memory 加载、一些微调和一个返回指令。 It'll still be super fast.它仍然会非常快。

This is portable and supported by the C++ standard, unlike inspecting the bits of a bitfield directly.这是可移植的,并受 C++ 标准支持,这与直接检查位域的位不同。

Alternatively, C++ allows casting between standard-layout structs that have common initial members.或者,C++ 允许在具有共同初始成员的标准布局结构之间进行转换。 (Though keep in mind that this falls apart as soon as inheritance or private/protected members get involved, The first solution, above. works for all those cases as well.) (尽管请记住,一旦 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;
}

See also: Can memcpy be used for type punning?另请参阅: memcpy 可以用于类型双关吗?

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

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