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

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

推荐答案

在我问了这个问题大约2年多之后,我想用我想要的方式来解释它,当我还是一个完全的新手时,这对那些想了解这个过程的人来说是最有益的.

首先,忘记"11111111"示例值,它并不完全适合流程的可视化解释.因此,假设初始值为10111011(187十进制),这将更好地说明该过程.

1 - how to read a 3 bit value starting from the second bit:

    ___  <- 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 - how to write a 3 bit value starting from the second bit:

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

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

    ___ 
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)

Bonus - how to generate the read and write masks:

当然,使用二进制到十进制转换器远非完美,尤其是在32位和64位容器的情况下-十进制值变得非常大.可以使用表达式轻松地生成掩码,编译器可以在编译期间有效地解析这些掩码:

  • read mask for "遮罩和移位": ((1 << fieldLength) - 1) << (fieldIndex - 1), assuming that the index at the first bit is 1 (not zero)
  • read mask for "移位和遮罩": (1 << fieldLength) - 1 (index does not play a role here since it is always shifted to the first bit
  • write mask : just invert the "遮罩和移位" mask expression with the ~ operator

它是如何工作的(3位字段从上述示例中的第二位开始)?

00000001 << 3
00001000  - 1
00000111 << 1
00001110  ~ (read mask)
11110001    (write mask)

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

还要注意的是,示例假定无符号整数,这是您想要使用整数作为可移植位字段的替代方案(标准绝不保证常规位字段是可移植的),左移位和右移位都会插入一个填充0,而右移位有符号整数则不是这种情况.

Even easier:

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

你可以 Select 一些简单的东西,比如:

struct A {
  uint bitData;
  FIELD(bitData, one, 0, 1)
  FIELD(bitData, two, 1, 2)
};

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

A a;
a.set_two(3);
cout << a.two();

用gcc的typeof pre-C++11替换decltype.

C++相关问答推荐

找出文件是否包含给定的文件签名

ATTiny1606定时器TCA 0中断未触发

每个 struct 变量在C中都有自己的命名空间吗?

N的值设置为0或1(未定义的行为),而我正在try 学习realloc和Malloc的用法

Sizeof(&Q;字符串&Q;)的正确输出是什么?

在Apple Silicon上编译x86的Fortran/C程序

如何在下面的C代码中正确管理内存?

C指针概念分段故障

为什么我从CSV文件中进行排序和搜索的代码没有显示数据的所有结果?

C11/C17标准允许编译器清除复合文字内存吗?

我在C中运行和调试时得到了不同的输出

如何将大写/小写土耳其字母相互转换?

为什么二进制文件的大小不会随着静态数据的大小而增加?

在NASM中链接Linux共享库时出错-';将R_ X86_64_;foo';

C 和 C++ 标准如何告诉您如何处理它们未涵盖的情况?

根据输入/输出将 C 编译过程分为预处理、编译、汇编和链接步骤

macos/arm64 上地址空间不使用第一位吗?

System V 消息队列由于某种原因定期重置

(GNU+Linux) 多个线程同时调用malloc()

在带中断的循环缓冲区中使用 易失性