在我提出这个问题大约 2 年多之后,我想以我希望它解释的方式解释它,当时我还是一个完整的新手,并且对想要了解这个过程的人最有益。
首先,忘记“11111111”示例值,它并不完全适合可视化的过程解释。所以让初始值为10111011(十进制的187),这将更能说明这个过程。
1 - 如何读取从第二位开始的 3 位值:
___ <- those 3 bits
10111011
值为101,即十进制的5,有两种可能的获取方式:
在这种方法中,需要的位首先用值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 位容器的情况下 - 十进制值变得非常大。可以轻松地生成带有表达式的掩码,编译器可以在编译期间有效地解析:
- “掩码和移位”的读取掩码:
((1 << fieldLength) - 1) << (fieldIndex - 1),假设第一位的索引为 1(非零)
- “移位和掩码”的读取掩码:
(1 << fieldLength) - 1(索引在这里不起作用,因为它总是移位到第一位
- 编写掩码:只需使用
~ 运算符反转“掩码和移位”掩码表达式
它是如何工作的(上面例子中的 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();
将 decltype 替换为 gcc 的 typeof pre-C++11。