reading/writing a value at specified bit offset

4.9k views Asked by At

I need to read and write numerical values of specified bit-length (not necessarily multiples of 8) at a specified bit-offset in a memory buffer, the most-significant bit first.

For example, writing the value of 5 at offset 6 and bit-length 4:

before: 11111111 11111111
bits:         ^^ ^^
after:  11111101 01111111

So the functions I'm looking for may be defined like this:

unsigned get_bits (unsigned char *buffer, int offset, int bit_size);
void set_bits (unsigned char *buffer, int offset, int bit_size, 
                             unsigned value);

And example usage:

set_bits (buffer, 6, 4, 5);
unsigned v = get_bits (buffer, 6, 4);
assert (v == 5);

The functions will be used to read/write a small number of values in a relatively big buffer (filtering network traffic of high volume), so I discarded (perhaps wrongly) using std::bitset.

Are there any existing libraries which could be used to achieve/simplify this task? Any suggestions regarding implementing it?

2

There are 2 answers

1
Daniel Hanrahan On BEST ANSWER

Bit-fiddling is seldom easy. Here's a complete working example of how to do what you want.

The big problem here is reading and writing blocks of numbers across byte boundaries. Problems are always easier if you break them into bite-size chunks, if you'll pardon the pun.

Firstly I created a class like std::bitset that can wrap a user-defined buffer. It lets us fiddle with individual bits in a big buffer of binary data.

#include <cassert>  // for assert
#include <cstring>  // for memset

// A wrapper for a data buffer providing bit-level access.
class BitBuffer {
public:
    BitBuffer (unsigned char *src_buffer, size_t byte_size)
        : m_data( src_buffer ), m_size( byte_size )
    {
        if (m_size) assert(m_data);
    }

    // Clear the buffer (set all bits to 0).
    void clear () {
        memset( m_data, 0, m_size );
    }

    // Get an individual bit's value.
    bool get (size_t bitpos) const {
        assert( bitpos / 8 < m_size );
        return m_data[ bitpos / 8 ] & ( 1 << ( bitpos % 8 ) );
    }

    // Set an individual bit's value.
    void set (size_t bitpos, bool val=true) {
        assert( bitpos / 8 < m_size );
        if( val ) {
            m_data[ bitpos / 8 ] |= ( 1 << ( bitpos % 8 ) );
        } else {
            m_data[ bitpos / 8 ] &= ~( 1 << ( bitpos % 8 ) );
        }
    }

    // Switch off a bit.
    void reset (size_t bitpos) { 
        set( bitpos, false );
    }

    // Flip a bit.
    void flip (size_t bitpos) {
        set( bitpos, ! get( bitpos ) );
    }

    // Return the size of the buffer in bytes.
    size_t byte_size () const { return m_size; }

    // Return the size of the buffer in bits.
    size_t bit_size () const { return m_size * 8; }

    // Return a const pointer to the buffer.
    unsigned char const *  buffer () const { return m_data; }

private:
    unsigned char * m_data;
    size_t m_size;
};

Then I wrote some functions to get and set blocks of bits from the buffer, MSB first.

unsigned get_bits (BitBuffer& buffer, size_t offset, size_t bit_size)
{
    unsigned bits = 0;
    for (size_t i = 0; i < bit_size; i++) {
        // We reverse the order of the bits, so the first bit read
        // from the buffer maps to the high bit in 'bits'.
        bits |= ( buffer.get( offset + i ) << (bit_size - 1 - i) );
    }
    return bits;
}

void set_bits (BitBuffer& buffer, size_t offset, size_t bit_size, unsigned bits)
{
    for (size_t i = 0; i < bit_size; i++) {
        // We reverse the order of the bits, so the high bit of 'bits'
        // gets written to the buffer first.
        bool bit_value = bits & ( 1 << (bit_size - 1 - i) );
        buffer.set( offset + i, bit_value );
    }
}

And a test harness:

#include <cstdio>

// Print the bits of the buffer to stdout.
void dump_buffer (BitBuffer& buffer)
{
    for (size_t i = 0; i < buffer.bit_size(); i++) {
        printf( "%i", buffer.get(i) );
    }
    printf("\n");
}

int main()
{
    const size_t byte_size = 4;  // size of buffer in bytes
    unsigned char * raw_buffer = new unsigned char[ byte_size ];

    BitBuffer buffer( raw_buffer, byte_size );
    buffer.clear();

    printf("Test #0: contents of 4-byte buffer:\n");

    // Show the buffer.
    dump_buffer( buffer );

    printf("\nTest #1: setting and flipping bits:\n");

    // Set some bits
    buffer.set( 5 );
    buffer.set( 10 );
    buffer.set( 12 );
    buffer.set( 31 );

    // Show the buffer.
    dump_buffer( buffer );

    // Read some bits.
    printf( "Value at 12 before flip: %i\n", buffer.get( 12 ) );
    buffer.flip( 12 );
    printf( "Value at 12 after flip: %i\n", buffer.get( 12 ) );

    printf("\nTest #2: setting all 1's, and writing 5, length 4 to offset 6:\n");

    // Fill with 1's.
    set_bits(buffer, 0, 32, 0xFFFFFFFF);

    // Set 5 at offset 6, bit size 4.
    set_bits(buffer, 6, 4, 5);
    assert( get_bits(buffer, 6, 4) == 5 );

    // Show the buffer.
    dump_buffer( buffer );

    // All done.
    delete raw_buffer;
    return 0;
}

To compile, just dump all this in one file and compile. Output of test run:

Test #0: contents of 4-byte buffer:
00000000000000000000000000000000

Test #1: setting and flipping bits:
00000100001010000000000000000001
Value at 12 before flip: 1
Value at 12 after flip: 0

Test #2: setting all 1's, and writing 5, length 4 to offset 6:
11111101011111111111111111111111

Let me know if you find it useful, or if you have any problems with it.

1
sachleen On

To set specific bits to 1, you can create a series of bits that are all 0 except for the ones that you want to set (those are 1) and OR it with the existing bits. To set specific bits to 0, you do the same thing only all the bits are 1 except for the ones you want 0 and you use the AND operator.

Example:

before:        11111111 11111111
bits to SET:         _^ _^       5 is 0101, so we SET just the 1 bits, _ is placeholder
bitmask: OR    00000001 01000000
result:        11111111 11111111 No different because they were all 1s already.
bits to RESET:       ^_ ^_       RESET the 0s
bitmask: AND   11111101 01111111
result:        11111101 01111111

Don't know of any libraries to help with that though.