c++ bitfield的掩码

发布于 2025-01-26 21:22:27 字数 867 浏览 1 评论 0原文

这是我找不到一个很好的答案:

给定一个带有比特菲尔德的结构,例如,

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

在C ++中是否有一种(便携式)的方法可以为其中一个Bitfields获取正确的掩码,最好是作为编译时常数功能还是模板?

类似的事情:

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

甚至可以用宏(yuck!)包裹,以使其“通用”。

但是我想您可以很容易地看到此方法的各种缺陷。

是否有更好的通用C ++做到这一点?甚至是不那么努力的方式?对于下一个C ++标准,是否有一些有用的事情?反射?

编辑:让我补充说,我正在尝试找到一种使Bitfield操纵更加灵活的方法,以使程序员可以同时使用掩码来修改多个字段。我之后是简短的符号,因此可以简洁地表达的情况下,没有很多样板。考虑使用I/O驱动程序中的硬件寄存器作为用例。

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?

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? Or even a not-so-nice way? Is there something useful coming up for the next C++ standard(s)? 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

甩你一脸翔 2025-02-02 21:22:27

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

来自 cppreference

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

  • 通过分配或初始化具有超出范围的值的签名位字段或递增签名的值而产生的值

  • 关于类对象中位字段的实际分配详细信息的所有信息

    • 例如,在某些平台上,位字段不跨越字节,在其他平台上,它们
    • 另外,在某些平台上,位田被从左到右挤满了

您的编译器可能会为您提供更强大的保证;但是,如果您确实依靠特定编译器的行为,则不能指望代码可以与其他编译器/架构对一起使用。据我所知,海湾合作委员会甚至没有记录他们的位田地包装,并且从一个体系结构到另一个体系结构都不同。因此,您的代码可能会在X86-64上的特定版本的GCC上使用,但实际上是其他所有内容,包括同一编译器的其他版本。

如果您真的希望能够以通用的方式从随机结构中提取比特场,那么最好的选择是将功能指针传递(而不是掩码);这样,该功能可以安全地访问字段,并将值返回其呼叫者(或设置值)。

类似的事情:

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,Defactor},则可以安全地获取Bitfield的值。 (当然,提取器函数必须匹配该指针背后的对象的类型。)与拥有{pointer,mask} pair相比,它的开销并不多;功能指针可能比64位计算机上的蒙版大4个字节(如果有的话)。提取器函数本身将只是一个内存负载,有些扭曲和返回指令。它仍然会超级快。

与直接检查比特菲尔德的位不同,这是可移植的,并由C ++标准支持。

另外,C ++允许在具有共同初始成员的标准层结构之间进行铸造。 (尽管请记住,一旦继承或私人/受保护的成员参与其中!上面的第一个解决方案也适用于所有这些情况。)

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进行类型?

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++.

From 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

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. 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.

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); that way, the function can access the field in a safe way and return the value to its caller (or set a value instead).

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. (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; the function pointer is maybe 4 bytes larger than the mask on a 64-bit machine (if at all). The extractor function itself will just be a memory load, some bit twiddling, and a return instruction. It'll still be super fast.

This is portable and supported by the C++ standard, unlike inspecting the bits of a bitfield directly.

Alternatively, C++ allows casting between standard-layout structs that have common initial members. (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.)

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?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文