47

假设我有一个二进制值为 11111111 的字节 b

例如,我如何读取从第二位开始的 3 位整数值或从第五位开始写入四位整数值?

4

8 回答 8

140

在我问了这个问题大约 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 的typeofpre-C++11。

于 2014-12-21T19:11:28.693 回答
17

您需要转换和屏蔽该值,例如...

如果要读取前两位,只需像这样将它们屏蔽掉:

int value = input & 0x3;

如果要偏移它,则需要右移 N 位,然后屏蔽所需的位:

int value = (intput >> 1) & 0x3;

像您在问题中提出的那样阅读三位。

int value = (input >> 1) & 0x7;
于 2012-08-05T11:11:44.530 回答
9

随意使用它:

#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
于 2016-07-07T19:24:29.450 回答
7

您必须执行移位和屏蔽 (AND) 操作。令b为任意字节,p为您要从中获取n位 (>= 1) 的位的索引 (>= 0)。

首先,您必须将b右移p次:

x = b >> p;

其次,您必须用n个来掩盖结果:

mask = (1 << n) - 1;
y = x & mask;

您可以将所有内容放在宏中:

#define TAKE_N_BITS_FROM(b, p, n) ((b) >> (p)) & ((1 << (n)) - 1)
于 2012-08-05T11:41:06.347 回答
3

“例如,我如何读取从第二位开始的 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;

(我假设“第二位”是位#2,即第三位真的。)

于 2012-08-05T11:11:24.437 回答
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);
于 2012-08-05T11:34:36.290 回答
1
int x = 0xFF;   //your number - 11111111

例如,我如何读取从第二位开始的 3 位整数值

int y = x & ( 0x7 << 2 ) // 0x7 is 111
                         // and you shift it 2 to the left
于 2012-08-05T11:10:34.733 回答
1

如果您不断从数据中获取位,则可能需要使用位域。您只需要设置一个结构并仅使用 1 和 0 加载它:

struct bitfield{
    unsigned int bit : 1
}
struct bitfield *bitstream;

然后稍后像这样加载它(用 int 或您正在加载的任何数据替换 char ):

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

于 2016-01-19T14:42:56.400 回答