mreff555
mreff555

Reputation: 1111

Advantages of boolean values to bit-fields

The code base I work in is quite old. While we compile nearly everything with c++11. Much of the code was written in c many years ago. When developing new classes in old areas I always find myself in a situation where I have to choose between matching old methodologies, or going with a more modern approach.

In most cases, I prefer sticking with more modern techniques when possible. However, one common old practice I often see, which I have a hard time arguing the use of, is bitfields. We pass a lot of messages, here, many times, they are full of single bit values. Take the example below:

class NewStructure
{
public:

    const bool getValue1() const
    {
        return value1;
    }

    void setValue1(const bool input)
    {
        value1 = input;
    }

private:
    bool value1;
    bool value2;
    bool value3;
    bool value4;
    bool value5;
    bool value6;
    bool value7;
    bool value8;
};

struct OldStructure
{
    const bool getValue1() const
    {
        return value1;
    }

    void setValue1(const bool input)
    {
        value1 = input;
    }

    unsigned char value1 : 1;
    unsigned char value2 : 1;
    unsigned char value3 : 1;
    unsigned char value4 : 1;
    unsigned char value5 : 1;
    unsigned char value6 : 1;
    unsigned char value7 : 1;
    unsigned char value8 : 1;
};

In this case the sizes are 8 bytes for the New Structure and 1 for the old.
I added a "getter" and "setter" to illustrate the point that from a user perspective, they can be identical. I realize that perhaps you could make the case of readability for the next developer, but other than that, is there a reason to avoid bit fields? I know that packed fields take a performance hit, but because these are all characters, padding rules are still in place.

Upvotes: 2

Views: 2781

Answers (3)

Red.Wave
Red.Wave

Reputation: 4225

template<typename enum_type,size_t n_bits>
class bit_flags{
    std::bitset<n_bits> bits;
    auto operator[](enum_type bit){return bits[bit];};
    auto& set(enum_type bit)){return set(bit);};
    auto& reset(enum_type bit)){return set(bit);};
     //go on with flip et al...
static_assert(std::is_enum<enum_type>{});
 };

enum class  v_flags{v1,v2,/*...*/vN};

bit_flags<v_flags,v_flags::vN+1> my_flags;

my_flags.set(v_flags::v1);
my_flags.[v_flags::v2]=true;

std::bitset is as efficient as bool bit fields. You can wrap it in a class to force using every bit by names defined in an enum. Now you have a small but scalable utility to use for multiple defferent sets of bool flags. C++17 makes it even more convenient:

template<auto last_flag, typename enum_type=decltype(last_flag)>
class bit_flags{
    std::bitset<last_flag+1> bits;
    //...
};

bit_flags<v_flags::vN+1> my_flags;

Upvotes: 1

Perette
Perette

Reputation: 841

For you, as the programmer, there's not much difference. But the machine code to access a whole byte is much simpler/shorter than to access an individual bit, so using bitfields bulks up the generated code.

In a pseudo-assembly-language, your setter might turn into something like:

    ldb input1,b         ; get the new value into accumulator b
    movb b,value1        ; put it into the variable
    rts                  ; return from subroutine

But it's not so easy for bitfields:

    ldb input1,b        ; get the new value into accumulator b
    movb bitfields,a    ; get current bitfield values into accumulator a
    cmpb b,#0           ; See what to do.
    brz clearvalue1:    ; If it's zero, go to clearing the bit
    orb #$80,a          ; set the bit representing value1.
    bra resume:         ; skip the clearing code.
clearvalue1:
    andb #$7f,a         ; clear the bit representing value1
resume:
    movb a,bitfields    ; put the value back
    rts                 ; return

And it has to do that for each of your 8 members' setters, and something similar for getters. It adds up. Additionally, even today's dumbest compilers would probably inline the full-byte setter code, rather than actually make a subroutine call. For the bitfield setter, it may depend whether you're compiling optimizing for speed vs. space.

And you've only asked about booleans. If they were integer bitfields, then the compiler has to deal with loading, masking out prior values, shifting the value to align into its field, masking out unused bits, and/or the value into place, then writing it back to memory.

So why would you use one vs. the other?

  • Bitfields are slower, but pack data more efficiently.
  • Non-bitfields are faster, and require less machine code to access.

As the developer, it's your judgement call. If you will be keeping many instances of Structure in memory at once, the memory savings may be worth it. If you aren't going to have many instances of that structure in memory at once, the compiled code bloat offsets memory savings and you're sacrificing speed.

Upvotes: 3

SergeyA
SergeyA

Reputation: 62583

There are several things to consider when using bitfields. Those are (order of importance would depend on situation)

  • Performance

Bitfields operation incur performance penalty when set or read (as compared to direct types). A simple example of codegen shows the extra instructions emitted: https://gcc.godbolt.org/z/DpcErN However, bitfields provide for more compact data, which becomes more cache-friendly, and that could completely outweigh any drawbacks of additional operations. The only way to understand the real performance impact is by benchmarking actual application in the real use case.

  • ABI Interoperability

Endiannes of bit fields is implementation defined, so layout of the same struct produced by two compiler can differ.

  • Usability

There is no reference binding to a bitfield, nor you can take it's address. This could affect code and make it less clear.

Upvotes: 8

Related Questions