开发者

How to replace bits in a bitfield without affecting other bits using C

I wanted to replace bit/bits (more than one) in a 32/64 bit data field without affecting other bits. Say for example:

I have a 64-bit register where bits 5 and 6 can take values 0, 1, 2, and 3.

5:6
---
0 0
0 1
1 0
1 1

Now, when I read the register, I get say value 0x146 (0001 0 10 0 0110). Now I want to change the value at bit position 5 and 6 to 0开发者_开发技巧1. (Right now it is 10, which is 2 in decimal, and I want to replace it to 1 e 01) without other bits getting affected and write back the register with only bits 5 and 6 modified (so it becomes 126 after changing).

I tried doing this:

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

If I do this, the value at bit positions 5 and 6 will become 11 (0x3), not 01 (0x1) which I wanted.

  • How do I go about doing read, modify, and write?
  • How do I replace only certain bit/bits in a 32/64 bit fields without affecting the whole data of the field using C?

Setting a bit is okay, but more than one bit, I am finding it little difficult.


Use a bitmask. It is sort of like:

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)

This preserves the old bits and OR's in the new ones.


reg_data &= ~( (1 << shift1) | (1 << shift2) );
reg_data |= ( (1 << shift1) | (1 << shift2) );

The first line clears the two bits at (shift1, shift2) and the second line sets them.


Here is a generic process which acts on a long array, considering it a long bitfield, and addresses each bit position individually:

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

It simply takes the index, uses the lower three bits of the index to identify eight different bit positions inside each location of the char array, and the upper remainder bits addresses in which array location does the bit denoted by x occur.

To set a bit, you need to OR the target word with another word with 1 in that specific bit position and 0 in all other with the the target. All 0's in the other positions ensure that the existing 1's in the target are as it is during OR, and the 1 in the specific positions ensures that the target gets the 1 in that position. If we have mask = 0x02 = 00000010 (1 byte) then we can OR this to any word to set that bit position:

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

To clear a bit, you need to AND the target word with another word with 0 in that specific bit position and 1 in all. All 1's in all other bit positions ensure that during AND the target preserves its 0's and 1's as they were in those locations, and a 0 in the bit position to be cleared would also set that bit position 0 in the target word. If we have the same mask = 0x02, then we can prepare this mask for clearing by ~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


  1. Apply a mask against the bitfield to maintain the bits that you do not want to change. This will also clear out the bits that you will be changing.

  2. Ensure that you have a bitfield that contains only the bits that you want to set/clear.

  3. Either use the or operator to "or" the two bitfields, or just simply add them.

For instance, if you wanted to only change bits 2 thru 5 based on input of 0 thru 15.

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


The question was about how to implement it in C, but as all searches for "replace bits" lead to here, I will supply my implementation in VB.NET.

It has been unit test tested. For those who are wondering what the ToBinaryString extension looks like: 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


You'll need to do that one bit at a time. Use the or, like you're currently doing, to set a bit to one, and use the following to set something to 0:

reg_data &= ~ (1 << shift)


You can use this dynamic logic for any number of bit and in any bit field.

Basically, you have three parts in a bit sequence of number -

MSB_SIDE | CHANGED_PART | LSB_SIDE

The CHANGED_PART can be moved up to the extreme MSB or LSB side.

The steps to replace a number of bit(s) are as follows -

  1. Take only the MSB_SIDE part and replace all the remaining bits with 0.

  2. Update the new bit sequence by adding your desired bit sequence in particular position.

  3. Update the entire bit sequence with LSB_SIDE of the original bit sequence.

     org_no = 0x53513C;
     upd_no = 0x333;
     start_pos = 0x6, bit_len = 0xA;
     temp_no = 0x0;
    
     temp_no = org_no & (0xFFFFFFFF << (bit_len + start_pos));  // This is step 1
     temp_no |= upd_no << start_pos;  // This is step 2
     org_no = temp_no | (org_no & ~(0xFFFFFFFF << start_pos));  // This is step 3`
    

Note: The masking with 0xFFFFFFFF is considered as 32 bit. You can change accordingly with your requirement.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜