是否有可替代C++位域的替代方案

Ant*_*ine 19 c++ bit-fields

有许多情况(特别是在低级编程中),其中数据的二进制布局很重要.例如:硬件/驱动程序操作,网络协议等.

在C++中,我可以使用char*和按位操作(掩码和移位)读/写任意二进制结构,但这很乏味且容易出错.显然,我试图限制这些操作的范围并将它们封装在更高级别的API中,但它仍然很痛苦.

C++位域似乎为这个问题提供了一个开发人员友好的解决方案,但不幸的是它们的存储是特定于实现的.

NathanOliver提到std::bitset哪些基本上允许你访问一个整数的单个位,operator[]但是很好但缺少多位字段的访问器.

使用元编程和/或宏,可以抽象库中的按位运算.因为我不想重新发明轮子,所以我正在寻找一个(最好是STL或boost)库.

为了记录,我正在研究这个DNS解析器,但问题及其解决方案应该是通用的.

编辑:简短的回答:事实证明,bitfield的存储在实践中是可靠的(即使它不是标准规定的),因为系统/网络库使用它们,并且在使用主流编译器编译时表现良好的程序.

o11*_*11c 9

从C++ 14标准(N3797草案),第9.6节[class.bit],第1段:

类对象中位域的分配是实现定义的.位字段的对齐是实现定义的.比特字段被打包到一些可寻址的分配单元中.[注意:位字段跨越某些机器上的分配单元而不是其他机器上的分配单元.在某些机器上从右到左分配位字段,在其他机器上从左到右分配. - 结束说明]

虽然注释是非规范的,但我所知道的每个实现都使用两种布局之一:big-endian或little endian位顺序.

注意:

  • 您必须手动指定填充.这意味着您必须知道类型的大小(例如,通过使用<cstdint>).
  • 您必须使用无符号类型.
  • 用于检测位顺序的预处理器宏是依赖于实现的.
  • 通常,位顺序字节顺序与字节顺序字节顺序相同.我相信有一个编译器标志可以覆盖它,但是我找不到它.

例如,查看netinet/tcp.h附近的其他标题.

通过OP编辑:例如tcp.h定义

struct
{
    u_int16_t th_sport;     /* source port */
    u_int16_t th_dport;     /* destination port */
    tcp_seq th_seq;     /* sequence number */
    tcp_seq th_ack;     /* acknowledgement number */
# if __BYTE_ORDER == __LITTLE_ENDIAN
    u_int8_t th_x2:4;       /* (unused) */
    u_int8_t th_off:4;      /* data offset */
# endif
# if __BYTE_ORDER == __BIG_ENDIAN
    u_int8_t th_off:4;      /* data offset */
    u_int8_t th_x2:4;       /* (unused) */
# endif
    // ...
}
Run Code Online (Sandbox Code Playgroud)

由于它与主流编译器配合使用,这意味着bitset的内存布局在实践中是可靠的.

编辑:

这在一个字节序中是可移植的:

struct Foo {
    uint16_t x: 10;
    uint16_t y: 6;
};
Run Code Online (Sandbox Code Playgroud)

但这可能不是因为它跨越了一个16位的单位:

struct Foo {
    uint16_t x: 10;
    uint16_t y: 12;
    uint16_t z: 10;
};
Run Code Online (Sandbox Code Playgroud)

这可能不是因为它有隐式填充:

struct Foo {
    uint16_t x: 10;
};
Run Code Online (Sandbox Code Playgroud)

  • 是的,只要您不跨越单位边界,并填充到该尺寸即可。如果优化影响结构布局,则整个Universe将燃烧。 (2认同)
  • 实际上,`tcp.h`标题让我信服,实际上bitfields是可用的,即使在SO上有很多关于它们的过于谨慎的帖子. (2认同)

650*_*502 5

使用C++实现具有已知位置的位字段很简单:

template<typename T, int POS, int SIZE>
struct BitField {
    T *data;

    BitField(T *data) : data(data) {}

    operator int() const {
        return ((*data) >> POS) & ((1ULL << SIZE)-1);
    }

    BitField& operator=(int x) {
        T mask( ((1ULL << SIZE)-1) << POS );
        *data = (*data & ~mask) | ((x << POS) & mask);
        return *this;
    }
};
Run Code Online (Sandbox Code Playgroud)

上述玩具实现允许例如在unsigned long long变量中定义12位字段

unsigned long long var;

BitField<unsigned long long, 7, 12> muxno(&var);
Run Code Online (Sandbox Code Playgroud)

并且生成的访问字段值的代码就是

0000000000000020 <_Z6getMuxv>:
  20:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax  ; Get &var
  27:   48 8b 00                mov    (%rax),%rax     ; Get content
  2a:   48 c1 e8 07             shr    $0x7,%rax       ; >> 7
  2e:   25 ff 0f 00 00          and    $0xfff,%eax     ; keep 12 bits
  33:   c3                      retq   
Run Code Online (Sandbox Code Playgroud)

基本上你必须手工编写


Cpl*_*zle 5

我们在生产代码中有这个,我们必须将 MIPS 代码移植到 x86-64

https://codereview.stackexchange.com/questions/54342/template-for-endianness-free-code-data-always-packed-as-big-endian

对我们来说效果很好。

它基本上是一个没有任何存储的模板,模板参数指定相关位的位置。

如果您需要多个字段,您可以将模板的多个特化放在一个联合中,并与一个字节数组一起提供存储。

该模板具有用于赋值的重载和unsigned用于读取值的转换运算符。

此外,如果字段大于一个字节,则它们以大端字节序存储,这在实现跨平台协议时有时很有用。

这是一个使用示例:

union header
{
    unsigned char arr[2];       // space allocation, 2 bytes (16 bits)

    BitFieldMember<0, 4> m1;     // first 4 bits
    BitFieldMember<4, 5> m2;     // The following 5 bits
    BitFieldMember<9, 6> m3;     // The following 6 bits, total 16 bits
};

int main()
{
    header a;
    memset(a.arr, 0, sizeof(a.arr));
    a.m1 = rand();
    a.m3 = a.m1;
    a.m2 = ~a.m1;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)