[英]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.