Bitfield widths in C/C++ structures
Jeff DeWall 3 min read September 02, 2019Recently, I learned about a feature in C and C++ that I hadn't run into before: bit widths for fields in structures. The idea is that you can specify how many bits a particular field should have allocated to it. For example, you can have a field that is only 5 bits wide and therefore only takes values in [0, 32). You could then have a field that is 3 bits wide that is placed next to the first field in memory.
I decided to play around with them a bit, trying to create a packed RGB 565 structure using bitfield widths rather than doing the bitmasking and shifting by hand. I noticed a couple of interesting things in the process:
- It's implementation dependent as to what order the fields show up; left-to-right or right-to-left in memory.
- It's implementation dependent as to whether fields span across type size boundaries.
You can read more in depth details here on cppreference
An example
Here is the code I hacked together to see how it works. There are three versions of basically the same RGB565 structure, but with minor changes, first with field type, and then with order:
#include <cstdio>
struct rgb565_a {
unsigned char r : 5;
unsigned char g : 6;
unsigned char b : 5;
};
struct rgb565_b {
unsigned short r : 5;
unsigned short g : 6;
unsigned short b : 5;
};
struct rgb565_c {
unsigned short b : 5;
unsigned short g : 6;
unsigned short r : 5;
};
template <typename T, typename RawVal>
void printStructInfo() {
printf("-----------------------------\n");
printf("sizeof %s = %lu\n", __PRETTY_FUNCTION__, sizeof(T));
T val;
val.r = 15;
val.g = 63;
val.b = 31;
printf("packed struct contains: %d, %d, %d\n", val.r, val.g, val.b);
RawVal rawVal = *(RawVal*)&val;
printf("raw value is 0x%x\n\n\n", rawVal);
}
int main() {
printStructInfo<rgb565_a, unsigned short>();
printStructInfo<rgb565_b, unsigned short>();
printStructInfo<rgb565_c, unsigned short>();
}
Running this with GCC 7.4.0 you get:
$ g++ ./bitfield_tests.cpp
$ ./a.out
-----------------------------
sizeof void printStructInfo() [with T = rgb565_a; RawVal = short unsigned int] = 3
packed struct contains: 15, 63, 31
raw value is 0x3f6f
-----------------------------
sizeof void printStructInfo() [with T = rgb565_b; RawVal = short unsigned int] = 2
packed struct contains: 15, 63, 31
raw value is 0xffef
-----------------------------
sizeof void printStructInfo() [with T = rgb565_c; RawVal = short unsigned int] = 2
packed struct contains: 15, 63, 31
raw value is 0x7fff
What you see is that the first structure is having each field placed into its own byte, not spanning the g
field across the first and second bytes.
The second structure shows that the structure is packed little endian on my machine, meaning r
is in the lower bits, g
in the middle, and b
in the upper bits, so actually a BGR565 structure.
The last one is what I actually wanted: 16 bits packed tightly together, with red in the upper bits and blue in the bottom.
Summary
Letting the compiler do the bitmasking and shifting for you looks like a useful feature on the surface, but I'd be careful to have a unit test when using this in production. I'd want to make sure that on any particular architecture I build for, that I would get the raw values that I expect so I wouldn't end up with red and blue switched around when changing compilers or targets.