But C/ C-compilers don't guarantee your struct wont have holes (by default), so you may have to do something like __attribute__((packed)) to ensure they are packed structs:
This is not true of adjacent bitfields, at least for C99:
An implementation may allocate any addressable storage unit large enough to hold a bit-field. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit.
That’s usually something your ABI will describe in fairly precise terms, though if (as in your example) you want non-naturally-aligned fields, you may indeed want to both use a packed struct and prepare for alignment faults on less-tolerant architectures.
in microcontrollers it's very common to see code generated that creates structs for the registers. They will typically output fields that are a full machine word in size (or maybe in some cases as small as a byte), and individual bits will be addressed with bitmasking (ie `my_device.some_reg |= SOME_REG_FLAG_NAME` or `my_device.some_reg &= ~SOME_REG_FLAG_NAME` to clear it). It is sometimes necessary to be thoughtful about batching writes so that certain bits are set before the periferal begins some process. A trivial example would be:
Bitfields! This is valid C: