如何在C/C++中读/写任意位

dte*_*ech 36 c c++ memory read-write bit

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

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

dte*_*ech 108

在我提出这个问题大约2年多后,我想解释一下,当我还是一个完整的新手时,我想要它的解释方式,并且对那些想要理解这个过程的人最有益.

首先,忘记"11111111"示例值,它实际上并不适合于该过程的可视化解释.因此,让初始值为10111011(十进制187),这将更加说明过程.

1 - 如何从第二位开始读取3位值:

    ___  <- those 3 bits
10111011 
Run Code Online (Sandbox Code Playgroud)

值为101或十进制5,有两种可能的方法:

  • 面具和转移

在这种方法中,首先使用值00001110(十进制值14)屏蔽所需的位,然后将其移位到位:

    ___
10111011 AND
00001110 =
00001010 >> 1 =
     ___
00000101
Run Code Online (Sandbox Code Playgroud)

这个表达式是: (value & 14) >> 1

  • 转移和面具

这种方法类似,但操作顺序相反,意味着原始值被移位,然后用00000111(7)屏蔽,只留下最后3位:

    ___
10111011 >> 1
     ___
01011101 AND
00000111
00000101
Run Code Online (Sandbox Code Playgroud)

这个表达式是: (value >> 1) & 7

两种方法都涉及相同的复杂性,因此性能不会有所不同.

2 - 如何从第二位开始写入3位值:

在这种情况下,初始值是已知的,当代码中出现这种情况时,您可能想出一种方法将已知值设置为另一个使用较少操作的已知值,但实际上这很少是大多数情况下,代码既不知道初始值,也不知道要写入的值.

这意味着为了将新值成功"拼接"到字节中,必须将目标位设置为零,然后将移位的值"拼接"到位,这是第一步:

    ___ 
10111011 AND
11110001 (241) =
10110001 (masked original value)
Run Code Online (Sandbox Code Playgroud)

第二步是将我们想要写入的值移位到3位,比如我们要将其从101(5)更改为110(6)

     ___
00000110 << 1 =
    ___
00001100 (shifted "splice" value)
Run Code Online (Sandbox Code Playgroud)

第三步也是最后一步是将屏蔽的原始值与移位的"拼接"值拼接:

10110001 OR
00001100 =
    ___
10111101
Run Code Online (Sandbox Code Playgroud)

整个过程的表达方式是: (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)
Run Code Online (Sandbox Code Playgroud)

相同的示例适用于更宽的整数和字段的任意位宽和位置,其中移位和掩码值相应地变化.

另请注意,这些示例假定无符号整数,这是您要使用的整数,以便将整数用作便携式位域替代方案(标准无法保证常规位字段可移动),左右移位插入填充0,右移有符号整数不是这种情况.

更简单:

使用这组宏(但仅限于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); }
Run Code Online (Sandbox Code Playgroud)

你可以去做一些简单的事情:

struct A {
  uint bitData;
  FIELD(bitData, one, 0, 1)
  FIELD(bitData, two, 1, 2)
};
Run Code Online (Sandbox Code Playgroud)

并将位字段实现为您可以轻松访问的属性:

A a;
a.set_two(3);
cout << a.two();
Run Code Online (Sandbox Code Playgroud)

替换decltype为gcc的typeofpre-C++ 11.

  • 将最终样本翻译成C需要做更多的工作.你需要`typedef struct AA;`来定义`a`才能工作.同样在C中,您无法在结构范围内定义函数,这意味着需要进行一些重大更改(您需要将结构传递给函数等 - 符号更改是不可忽略的). (2认同)
  • @ tommy.carstensen - 我不确定我理解你的问题,这个值只是一个无符号整数,简洁表示为单个字节. (2认同)

Geo*_*rey 13

您需要移动并屏蔽该值,例如......

如果你想读取前两位,你只需要像这样掩盖它们:

int value = input & 0x3;
Run Code Online (Sandbox Code Playgroud)

如果你想要偏移它,你需要右移N位,然后屏蔽掉你想要的位:

int value = (intput >> 1) & 0x3;
Run Code Online (Sandbox Code Playgroud)

像你在问题中提到的那样读三位.

int value = (input >> 1) & 0x7;
Run Code Online (Sandbox Code Playgroud)


Cla*_*dix 6

你必须做一个移位和掩码(AND)操作.设b为任何字节,p为要从中获取n位(> = 1)的位的索引(> = 0 ).

首先,你必须右移bp倍:

x = b >> p;
Run Code Online (Sandbox Code Playgroud)

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

mask = (1 << n) - 1;
y = x & mask;
Run Code Online (Sandbox Code Playgroud)

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

#define TAKE_N_BITS_FROM(b, p, n) ((b) >> (p)) & ((1 << (n)) - 1)
Run Code Online (Sandbox Code Playgroud)


Ham*_*mid 6

只需使用它就可以了:

#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     **/
Run Code Online (Sandbox Code Playgroud)

例如:

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
Run Code Online (Sandbox Code Playgroud)