如何使用C语言替换位域中的某些位而不影响其他位?

23

我希望替换掉32/64位数据字段中的一些比特位(多个),而不影响其他比特位。举个例子:

我有一个64位寄存器,其中第5位和第6位可以取值为0、1、2和3。

5:6
---
0 0
0 1
1 0
1 1
现在,当我读取寄存器时,我得到的值是0x146(0001 0 10 0 0110)。现在我想将位于第5和6位的值更改为01。(现在它是10,即十进制数2,我想将其替换为1 e 01),而不会影响其他位,并将只修改了第5和6位的寄存器写回(所以在更改后变成126)。

我尝试过以下方法:

reg_data = 0x146
reg_data |= 1 << shift   // In this case, 'shift' is 5

如果我这样做,位于5和6位置的值将变为11(0x3),而不是我想要的01(0x1)。

  • 如何进行读取、修改和写入操作?
  • 如何在不影响字段整体数据的情况下,仅替换32/64位字段中特定的位?使用C语言怎么做?

设置一位是可以的,但是多位,我发现有点困难。


另请参阅此答案中有关在C中设置字节的特定位而不影响其余部分的问题的解答,其中包含了一般适用的宏定义(而不是容易出错的手动内联方式)。 - Peter Mortensen
7个回答

36

使用位掩码。它有点像:

new_value = 0, 1, 2 or 3  // (this is the value you will set in)
bit_mask = (3<<5)         // (mask of the bits you want to set)
reg_data = (reg_data & (~bit_mask)) | (new_value<<5)

这将保留旧的位并在新的位上进行按位或运算。


10
reg_data &= ~( (1 << shift1) | (1 << shift2) );
reg_data |= ( (1 << shift1) | (1 << shift2) );
第一行代码清除了 (shift1, shift2) 处的两个位,第二行代码则将它们设置。

3
如果你知道它们是相邻的,这个问题就会变得更简单:例如,在这种情况下,reg_data = reg_data & (~(3 << 5)) | (1 << 5) - Amadan

8

这里是一个通用的处理过程,它将一个长数组视为一个长的位域,并逐个访问每个位位置:

#define set_bit(arr,x) ((arr[(x)>>3]) |= (0x01 << ((x) & 0x07)))
#define clear_bit(arr,x) (arr[(x)>>3] &= ~(0x01 << ((x) & 0x07)))
#define get_bit(arr,x) (((arr[(x)>>3]) & (0x01 << ((x) & 0x07))) != 0)

它简单地采用索引,使用索引的低三位来识别每个char数组位置内八个不同的位位置,剩余的上位比特位用于寻址x所表示的位出现在哪个数组位置中。

要设置一个位,需要将目标单词与另一个具有特定位位置上为1且其它所有位上为0的单词进行按位“或”操作。在其它位置上的所有0确保目标中现有的1在按位“或”时仍然存在,而特定位置上的1确保目标在该位置上获得1。如果我们有掩码= 0x02 = 00000010(1字节),那么我们可以对任何单词进行按位“或”操作以设置该位位置:

target = 1 0 1 1 0 1 0 0
OR       + + + + + + + +
mask     0 0 0 0 0 0 1 0
         ---------------
answer   1 0 1 1 0 1 1 0

为了清除某个二进制位,您需要将目标单词与另一个在该特定位上为0且在所有其他位上均为1的单词进行AND运算。在所有其他位上的所有1确保在AND运算期间,目标单词保留其0和1,就像它们在这些位置上一样,并且要清除的位位置上的0也会将目标单词中的该位设置为0。如果我们有相同的掩码= 0x02,则可以通过~mask来准备此掩码以进行清除:
mask  = 0 0 0 0 0 0 1 0
~mask = 1 1 1 1 1 1 0 1
AND     . . . . . . . .
target  1 0 1 1 0 1 1 0
        ---------------
answer  1 0 1 1 0 1 0 0

3
  1. 使用位域掩码来保留不需要更改的位。这也会清除你将要更改的位。

  2. 确保你有一个只包含你想要设置/清除的位的位域。

  3. 使用或运算符将两个位域“或”起来,或者简单地将它们相加。

例如,如果您想根据0到15的输入仅更改2到5位。

byte newVal = (byte)value & 0x0F;
newVal = (byte)value << 2;
oldVal = oldVal & 0xC3;
oldVal = oldval + newVal;

2
这个问题是关于如何在C中实现它,但由于所有搜索“replace bits”的结果都指向这里,我将提供我的VB.NET实现。

它已经通过了单元测试。对于那些想知道ToBinaryString扩展是什么样子的人:Convert.ToString(value,2)

''' <summary>
''' Replace the bits in the enumValue with the bits in the bits parameter, starting from the position that corresponds to 2 to the power of the position parameter.
''' </summary>
''' <param name="enumValue">The integer value to place the bits in.</param>
''' <param name="bits">The bits to place. It must be smaller or equal to 2 to the power of the position parameter.</param>
'''<param name="length">The number of bits that the bits should replace.</param>
''' <param name="position">The exponent of 2 where the bits must be placed.</param>
''' <returns></returns>
''' <remarks></remarks>'
<Extension>
Public Function PlaceBits(enumValue As Integer, bits As Integer, length As Integer, position As Integer) As Integer
    If position > 31 Then
        Throw New ArgumentOutOfRangeException(String.Format("The position {0} is out of range for a 32 bit integer.",
                                                            position))
    End If
    Dim positionToPlace = 2 << position
    If bits > positionToPlace Then
        Throw New ArgumentOutOfRangeException(String.Format("The bits {0} must be smaler than or equal to {1}.",
                                                            bits, positionToPlace))
    End If

    'Create  a bitmask (a series of ones for the bits to retain and a series of zeroes for bits to discard).'
    Dim mask As Integer = (1 << length) - 1
    'Use for debugging.'
    'Dim maskAsBinaryString = mask.ToBinaryString'

    'Shift the mask to left to the desired position'
    Dim leftShift = position - length + 1
    mask <<= leftShift
    'Use for debugging.'
    'Dim shiftedMaskAsBinaryString = mask.ToBinaryString'

    'Shift the bits to left to the desired position.'
    Dim shiftedBits = bits << leftShift
    'Use for debugging.'
    'Dim shiftedBitsAsBinaryString = shiftedBits.ToBinaryString'

    'First clear (And Not) the bits to replace, then set (Or) them.'
    Dim result = (enumValue And Not mask) Or shiftedBits
    'Use for debugging.'
    'Dim resultAsBinaryString = result.ToBinaryString'

    Return result
End Function

0

您可以在任何位域中使用此动态逻辑操作。

基本上,数字的位序列由三部分组成:

MSB_SIDE | CHANGED_PART | LSB_SIDE

其中 CHANGED_PART 可以移动到极端的 MSBLSB 端。

替换一定数量的位的步骤如下:

  1. 只取 MSB_SIDE 部分,将其余位替换为 0。

  2. 通过在特定位置添加所需的位序列来更新新的位序列。

  3. 使用原始位序列的 LSB_SIDE 更新整个位序列。

     org_no = 0x53513C;
     upd_no = 0x333;
     start_pos = 0x6, bit_len = 0xA;
     temp_no = 0x0;
    
     temp_no = org_no & (0xFFFFFFFF << (bit_len + start_pos));  // 这是第一步
     temp_no |= upd_no << start_pos;  // 这是第二步
     org_no = temp_no | (org_no & ~(0xFFFFFFFF << start_pos));  // 这是第三步`
    
注意:使用0xFFFFFFFF进行掩码处理被视为32位。您可以根据需要进行相应更改。

0

你需要一次一位地进行操作。像你目前正在做的那样使用来将一个位设置为1,并使用以下内容将某些内容设置为0:

reg_data &= ~ (1 << shift)

为什么需要一次只处理一个比特位? - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接