来自 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?
这是一个小谜题,我找不到好的答案:
给定一个带有位域的结构,例如
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?