Search code examples
c++coptimizationlanguage-lawyermemory-layout

Why are bitfields not tightly packed in a struct?


If I have this struct:

struct Tuple {
    unsigned int a : 4;
    unsigned int b : 4;
};

I expected it to have a size of one bytes with both values being packed together, but on any compiler I have tried, the size is four bytes (the same as an int on my machine). This surprises me a lot, as this construct now takes up more space even than two chars.

I do understand that:

  • memory-layout is implementation-defined
  • compilers leave 'holes' in structs to ensure efficient access to fields, leading to structs taking up more space than the sum of their fields
  • the standard forces the order of fields to be the same in source-code and in memory
  • C and C++ are different languages, but this exact phenomenon can be seen in both languages for the exact same piece of code.

Yet it would seem to me like packing these values together in one byte would be a performance-benefit as the size is now a quarter.

So I am wondering:

  • Is my mental model of performance wrong and the additional instructions for handling two fields in one bit would actually be costly?
  • Is there a rule in the standard that would ban this optimization? (eg some guarantee about concurrency or adressability banning the use of two fields in one byte)
  • What are bitfields good for if I can't actually define fields smaller than a byte?

Solution

  • While the C standard doesn't explicitly state what size a storage unit is for a bitfield, typically the declared type of the bitfield dictates what that is.

    In this case, because both bitfields are declared with unsigned int, that is the storage unit used to store both of them. This type (on most systems) is 4 bytes, so that's what the size of the struct will be.

    Had you declared it like this:

    struct Tuple {
        unsigned char a : 4;
        unsigned char b : 4;
    };
    

    Then both bitfields would fit in a storage unit of type unsigned char and the struct would then have a size of 1.