如何在C/C++中读写任意位
如何在C/C++中读写任意位
假设我有一个二进制值为11111111的字节b,我该如何读取从第二个比特位开始的3比特整数值,或者写一个从第五个比特位开始的四比特整数值?
在我提出此问题2年多之后,我想用当我还是一个彻底的新手时希望它解释的方式来解释它,这对希望了解此过程的人最有益。
首先,请忘记"11111111"的示例值,它并不适合于此过程的视觉解释。因此,让初始值为10111011
(187十进制),这将更具说明性。
1 - 如何从第二位开始读取3位值:
___ <- those 3 bits 10111011
该值为101,或10进制中的5,有2种可能的方法来获得它:
- 掩码和移位
在这种方法中,首先使用值00001110
(14十进制)掩码所需的位,然后将其移至原位:
___ 10111011 AND 00001110 = 00001010 >> 1 = ___ 00000101
该表达式为:(value & 14) >> 1
- 移位和掩码
这种方法类似,但操作的顺序是倒序的,这意味着原始值是被移位,然后使用00000111
(7)进行掩码,以仅留下最后3位:
___ 10111011 >> 1 ___ 01011101 AND 00000111 00000101
该表达式为:(value >> 1) & 7
这两种方法涉及相同的复杂性,因此在性能上不会有所不同。
2 - 如何从第二位开始写入3位值:
在这种情况下,初始值是已知的,当代码中存在这种情况时,您可以想出一种将已知值设置为使用更少操作的另一个已知值的方法,但实际上很少有这种情况,大多数时候代码既不知道初始值,也不知道要写入的值。
这意味着,为了成功将新值“剪切”到字节中,必须将目标位设置为零,然后将移位值“剪切”至原位,这是第一步:
___ 10111011 AND 11110001 (241) = 10110001 (masked original value)
第二步是将我们想要写入3位的值移位,比如说我们想要将它从101(5)更改为110(6)
___ 00000110 << 1 = ___ 00001100 (shifted "splice" value)
第三步是使用掩码的原始值与移位的"剪切"值相结合:
10110001 OR 00001100 = ___ 10111101
整个过程的表达式为:(value & 241) | (6 << 1)
额外 - 如何生成读写掩码:
显然,使用二进制转十进制转换器远非优雅,尤其是对于32位和64位容器-十进制值会变得极其复杂。可以使用表达式轻松生成掩码,编译器可以在编译期间有效地解决它们:
- "mask and shift"的读取掩码:
((1 << fieldLength) - 1) << (fieldIndex - 1)
,假设第一位的索引为1(而不是零) - "shift and mask"的读取掩码:
(1 << fieldLength) - 1
(索引在这里不起作用,因为它总是被移至第一位) - 写掩码:只需使用
~
运算符反转“mask and shift”掩码表达式
它是如何工作的?(使用上面的从第二位开始的3位字段的示例)
00000001 << 3 00001000 - 1 00000111 << 1 00001110 ~ (read mask) 11110001 (write mask)
同样的例子也适用于更宽的整数和任意位宽和字段位置,移位和掩码值相应地变化。
还要注意,这些例子假定无符号整数,这是您希望将整数用作可移植位域替代方案的情况(常规位域在标准中无法保证是可移植的),左移和右移都会插入填充0,而对于有符号整数的右移则不是这种情况。
更简单的方法:
使用这组宏(但只在C ++中使用,因为它依赖于成员函数的生成):
#define GETMASK(index, size) ((((size_t)1 << (size)) - 1) << (index)) #define READFROM(data, index, size) (((data) & GETMASK((index), (size))) >> (index)) #define WRITETO(data, index, size, value) ((data) = (((data) & (~GETMASK((index), (size)))) | (((value) << (index)) & (GETMASK((index), (size)))))) #define FIELD(data, name, index, size) \ inline decltype(data) name() const { return READFROM(data, index, size); } \ inline void set_##name(decltype(data) value) { WRITETO(data, index, size, value); }
您可以尝试使用如下简单的代码:
struct A { uint bitData; FIELD(bitData, one, 0, 1) FIELD(bitData, two, 1, 2) };
并将位域实现为您可以轻松访问的属性:
A a; a.set_two(3); cout << a.two();
请使用gcc的 typeof
而不是 decltype
预C ++ 11。