有许多情况(特别是在低级编程中),其中数据的二进制布局很重要.例如:硬件/驱动程序操作,网络协议等.
在C++中,我可以使用char*和按位操作(掩码和移位)读/写任意二进制结构,但这很乏味且容易出错.显然,我试图限制这些操作的范围并将它们封装在更高级别的API中,但它仍然很痛苦.
C++位域似乎为这个问题提供了一个开发人员友好的解决方案,但不幸的是它们的存储是特定于实现的.
NathanOliver提到std::bitset哪些基本上允许你访问一个整数的单个位,operator[]但是很好但缺少多位字段的访问器.
使用元编程和/或宏,可以抽象库中的按位运算.因为我不想重新发明轮子,所以我正在寻找一个(最好是STL或boost)库.
为了记录,我正在研究这个DNS解析器,但问题及其解决方案应该是通用的.
编辑:简短的回答:事实证明,bitfield的存储在实践中是可靠的(即使它不是标准规定的),因为系统/网络库使用它们,并且在使用主流编译器编译时表现良好的程序.
从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)
使用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)
基本上你必须手工编写
我们在生产代码中有这个,我们必须将 MIPS 代码移植到 x86-64
对我们来说效果很好。
它基本上是一个没有任何存储的模板,模板参数指定相关位的位置。
如果您需要多个字段,您可以将模板的多个特化放在一个联合中,并与一个字节数组一起提供存储。
该模板具有用于赋值的重载和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)
| 归档时间: |
|
| 查看次数: |
3567 次 |
| 最近记录: |