Bit fields only compact together so long as the type of the bit field is the same. So a bit field with:
struct Test
{
char Test1 : 1;
char Test2 : 1;
char Test3 : 1;
char Test4 : 1;
short Test5 : 4;
}
will not be one byte long, but four.
This is because of two things - first, the bit fields do not cross a type boundary. The bit fields in the char cannot intermix with the bit fields in the short.
Second, the short must be placed on a two byte boundary from the start of the structure for alignment purposes. so the compiler changes this to be:
struct Test
{
char Test1 : 1;
char Test2 : 1;
char Test3 : 1;
char Test4 : 1;
char BitPadding : 4;
char AlignmentPadding;
short Test5 : 4;
short BitPadding2 : 12;
}
which brings the struct up to four bytes long.
Now we go through each struct in turn.
struct BitFieldTest
{
bool flag1 : 1;
bool flag2 : 1;
bool flag3 : 1;
bool flag4 : 1;
unsigned char counter1 : 4;
unsigned int counter2 : 4;
};
Here, flag1
, flag2
, flag3
, and flag4
all have the same type, and condense down to a four bit value stored in a single byte. counter1
is of a different type, and has a natural alignment of one, so it moves to the next byte boundary. counter2
is also of a different type, and it has a natural alignment of four, so it moves to the next four byte boundary. If we then sum up the sizes of the individual sections and the intermediate padding, we have:
1 byte with four flags,
1 byte with a four bit counter,
2 bytes of alignment padding, and
4 bytes with a four bit counter.
This adds up to 8 bytes, exactly what the compiler reported.
The second structure has no bit fields, but shows off the alignment issue:
struct NormalFieldTest
{
bool flag1;
bool flag2;
bool flag3;
bool flag4;
unsigned char counter1;
unsigned int counter2;
};
Here, we have:
1 byte for boolean flag1,
1 byte for boolean flag2,
1 byte for boolean flag3,
1 byte for boolean flag4,
1 byte for counter1,
3 bytes for alignment padding, and
4 bytes for counter2.
This adds up to 12, which is, again, what the compiler reported.
The third structure is much the same as the second, but lacks the internal alignment padding:
struct NormalFieldTest2
{
bool flag1;
bool flag2;
bool flag3;
bool flag4;
unsigned char counter1;
};
Here, we have:
1 byte for boolean flag1,
1 byte for boolean flag2,
1 byte for boolean flag3,
1 byte for boolean flag4, and
1 byte for counter1.
This adds up to 5 bytes, as the compiler has reported.
As an additional note, natural alignment of internal types can also leak out into the structure itself. Consider the following structure:
struct TrailingAlignment
{
int Field1;
short Field2;
}
This structure outwardly looks to be 6 bytes, but will compile to 8. This is because the structure might be used in an array, and if it was 6 bytes in length, every other item in the array would contain a misaligned Field1
, causing major problems in systems that do not support misaligned access (like some flavors of ARM). To avoid this, the compiler inserts two bytes at the end of the structure to ensure the next instance of this structure in an array will be aligned on a four byte boundary, assuming the first structure was aligned properly.
This doesn't happen for NormalFieldTest2
because the natural alignment for bool
and char
is one byte, so it will always be aligned no matter where the structure is in memory.