如何在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可以是大或小端。