Reputation: 45
I have the following code:
struct Foo {
uint16_t a:5;
uint16_t b:5;
uint16_t c:5;
};
uint16_t bits = 0xdae;
const Foo& foo = *reinterpret_cast<const Foo*>(&bits);
printf("%x %x %x\n", foo.a, foo.b, foo.c);
The output is what I expect when I work it out on paper:
e d 3
If I use uint32_t instead of uint16_t bitfields, I get the same result.
But when I use uint8_t bitfields instead of uint16_t, I get an inconsistent result that is either:
e d 6
or
e d 16
Neither is correct. Why does using uint8_t for bitfields cause this strange behavior?
Upvotes: 1
Views: 590
Reputation: 35154
The memory layout of bit-fields is implementation defined. Confer, for example, this online draft c++ standard:
9.6 Bit-fields
(1) ... Allocation of bit-fields within a class object is implementation-defined. Alignment of bit-fields is implementation-defined.
Thus, as you do not introduce explicit padding by bit-fields of length 0, you have no control on how the compiler will lay out your struct in memory. Actually, I think that you yield undefined behaviour, as you are converting one pointer to another one with probably different alignment requirements, and accessing padding bits (as it would happen with the assignment of a reinterpreted cast) is undefined behaviour, too.
Note that you can control padding of bit fields in a standardised way:
(2) A declaration for a bit-field that omits the identifier declares an unnamed bit-field. Unnamed bit-fields are not members and cannot be initialized. [Note: An unnamed bit-field is useful for padding to conform to externally-imposed layouts. — end note ] As a special case, an unnamed bit-field with a width of zero specifies alignment of the next bit-field at an allocation unit boundary. Only when declaring an unnamed bit-field may the value of the constant-expression be equal to zero.
See the following code that makes use of this feature; Note that it yields a different result as I found no way of how to control padding such that I could get consecutive 5-bit-piecies. So I adapted the example to something that works on the level of byte borders:
struct Foo {
uint8_t a:5;
uint8_t :0;
uint8_t b:5;
uint8_t :0;
uint8_t c:5;
};
union DifferentView {
uint32_t bits;
struct Foo foo;
};
int main()
{
union DifferentView myView;
myView.bits = 0x0d0a0e;
printf("%x %x %x\n", myView.foo.a, myView.foo.b, myView.foo.c);
// Output: e a d
return 0;
}
Upvotes: 3
Reputation: 31447
The compiler may add padding between structure members (or it may not, at its discretion) and you don't account for that when you just try to access the entire struct as a bunch of bits. Basically you can't just do that (the behaviour is undefined and your code is simply buggy). You need to access the structure members by name or use compiler specific extensions to control the layout/padding.
Upvotes: 5