如何在C / C ++中读/写任意位
假设我有一个二进制值为11111111的字节b
我如何读取从第二位开始的3位整数值或从第五位开始写入四位整数值?
在我问这个问题后的两年多时间里,我想以我想要的方式来解释,当我还是一个完整的新手时,对于那些想要理解这个过程的人来说是最有利的。
首先,忘记“11111111”这个例子的价值,这个价值并不完全适合这个过程的视觉解释。 因此,让初始值为10111011
(十进制187),这将更多地说明这个过程。
1 – 如何从第二位开始读取一个3位的值:
___ <- those 3 bits 10111011
值为101或十进制的5,有两种可能的方式来获得它:
- 面具和转移
在这种方法中,首先将所需的位用值00001110
(14位十进制)掩码,然后将其移位:
___ 10111011 AND 00001110 = 00001010 >> 1 = ___ 00000101
这个expression式是: (value & 14) >> 1
- 移位和掩码
这种方法是相似的,但操作的顺序是相反的,这意味着原始值被移位,然后用00000111
(7)屏蔽, 00000111
下最后3位:
___ 10111011 >> 1 ___ 01011101 AND 00000111 00000101
这个expression式是: (value >> 1) & 7
这两种方法都具有相同的复杂性,因此在性能上不会有差异。
2 – 如何从第二位开始写入一个3位的值:
在这种情况下,初始值是已知的,当代码中出现这种情况时,您可能会想出一种方法将已知值设置为使用较less操作的另一个已知值,但实际上很less在大多数情况下,代码将不会知道初始值,也不知道要写入的值。
这意味着为了将新值成功地“拼接”成字节,必须将目标位设置为零,然后将移位的值“拼接”到位,这是第一步:
___ 10111011 AND 11110001 (241) = 10110001 (masked original value)
第二步是把我们想要写入的值转移到3位,比如我们想把它从101(5)改成110(6)
___ 00000110 << 1 = ___ 00001100 (shifted "splice" value)
第三步也是最后一步是将被屏蔽的原始值与移位的“拼接”值拼接在一起:
10110001 OR 00001100 = ___ 10111101
整个过程的expression式是: (value & 241) | (6 << 1)
(value & 241) | (6 << 1)
奖金 – 如何生成读写屏蔽:
当然,使用二进制到十进制的转换器还不够优雅,特别是在32位和64位的容器的情况下 – 十进制值变得非常大。 使用expression式可以轻松生成掩码,编译器在编译期间可以高效地parsing这些expression式:
-
((1 << fieldLength) - 1) << (fieldIndex - 1)
,假设第一个位的索引是1(不为0) - 读取“shift和mask”的掩码:
(1 << fieldLength) - 1
(索引在这里不起作用,因为它总是移到第一位 - 写掩码:用
~
运算符反转“掩码和移位”掩码expression式
它是如何工作的(从上面例子的第二位开始的3位字段)?
00000001 << 3 00001000 - 1 00000111 << 1 00001110 ~ (read mask) 11110001 (write mask)
相同的例子适用于更宽的整数和任意的位宽度和位置,并且移位和掩码值相应地改变。
还要注意的是,这些例子假设无符号整数,这是你想要使用整数作为便携式位字段替代(常规位字段不能保证标准是可移植的),左右移位插入一个填充0,而不是右移一个有符号整数的情况。
更简单:
使用这组macros(但只在C ++中,因为它依赖于成员函数的生成):
#define GETMASK(index, size) (((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))) #define FIELD(data, name, index, size) \ inline decltype(data) name() { 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的pre-C ++ 11replacedecltype
。
你需要转移和掩盖的价值,所以例如…
如果你想读取前两位,你只需要像这样掩饰它们:
int value = input & 0x3;
如果你想抵消它,你需要右移N位,然后掩盖你想要的位:
int value = (intput >> 1) & 0x3;
阅读三个像你问你的问题。
int value = (input >> 1) & 0x7;
你必须做一个转换和蒙版(AND)操作。 设b是任何字节, p是要从其中取n位(> = 1)的位的索引(> = 0)。
首先,您必须按p次向右移动b :
x = b >> p;
其次,你必须用n个掩盖结果:
mask = (1 << n) - 1; y = x & mask;
你可以把所有东西都放在macros里面:
#define TAKE_N_BITS_FROM(b, p, n) ((b) >> (p)) & ((1 << (n)) - 1)
“我怎样从第二位读取3位整数值?
int number = // whatever; uint8_t val; // uint8_t is the smallest data type capable of holding 3 bits val = (number & (1 << 2 | 1 << 3 | 1 << 4)) >> 2;
(我认为“第二位”是第二位,即第三位)。
要读取字节使用std :: bitset
const int bits_in_byte = 8; char myChar = 's'; cout << bitset<sizeof(myChar) * bits_in_byte>(myChar);
要写,你需要使用按位运算符,如&^ | &<< >>。 一定要学习他们做什么。
例如要有00100100,您需要将第一位设置为1,并用<< >>操作符将其移动5次。 如果你想继续写你只是继续设置第一个位,并将其移位。 这就像一个老式的打字机:你写,并转移纸张。
对于00100100:将第一位设置为1,移位5次,将第一位设置为1,并移位2次:
const int bits_in_byte = 8; char myChar = 0; myChar = myChar | (0x1 << 5 | 0x1 << 2); cout << bitset<sizeof(myChar) * bits_in_byte>(myChar);
只是使用这个和感觉:
#define BitVal(data,y) ( (data>>y) & 1) /** Return Data.Y value **/ #define SetBit(data,y) data |= (1 << y) /** Set Data.Y to 1 **/ #define ClearBit(data,y) data &= ~(1 << y) /** Clear Data.Y to 0 **/ #define TogleBit(data,y) (data ^=BitVal(y)) /** Togle Data.Y value **/ #define Togle(data) (data =~data ) /** Togle Data value **/
例如:
uint8_t number = 0x05; //0b00000101 uint8_t bit_2 = BitVal(number,2); // bit_2 = 1 uint8_t bit_1 = BitVal(number,1); // bit_1 = 0 SetBit(number,1); // number = 0x07 => 0b00000111 ClearBit(number,2); // number =0x03 => 0b0000011
int x = 0xFF; //your number - 11111111
我如何读取从第二位开始的3位整数值
int y = x & ( 0x7 << 2 ) // 0x7 is 111 // and you shift it 2 to the left
如果您不断从数据中抓取一些数据,则可能需要使用位域。 你只需要build立一个结构体,并只加载一个和零:
struct bitfield{ unsigned int bit : 1 } struct bitfield *bitstream;
然后加载它像这样(用intreplacechar或你正在加载的任何数据):
long int i; int j, k; unsigned char c, d; bitstream=malloc(sizeof(struct bitfield)*charstreamlength*sizeof(char)); for (i=0; i<charstreamlength; i++){ c=charstream[i]; for(j=0; j < sizeof(char)*8; j++){ d=c; d=d>>(sizeof(char)*8-j-1); d=d<<(sizeof(char)*8-1); k=d; if(k==0){ bitstream[sizeof(char)*8*i + j].bit=0; }else{ bitstream[sizeof(char)*8*i + j].bit=1; } } }
然后访问元素:
bitstream[bitpointer].bit=...
要么
...=bitstream[bitpointer].bit
所有这一切都假设在i86 / 64工作,而不是arm,因为arm可以是大或小端。