Eric Auld
Eric Auld

Reputation: 1244

Byte alignment in bit-fields

In the cppreference article on bit fields:

#include <bit>
#include <cstdint>
#include <iostream>
 
struct S
{
    // will usually occupy 2 bytes:
    unsigned char b1 : 3; // 1st 3 bits (in 1st byte) are b1
    unsigned char    : 2; // next 2 bits (in 1st byte) are blocked out as unused
    unsigned char b2 : 6; // 6 bits for b2 - doesn't fit into the 1st byte => starts a 2nd
    unsigned char b3 : 2; // 2 bits for b3 - next (and final) bits in the 2nd byte
};
 
int main()
{
    std::cout << sizeof(S) << '\n'; // usually prints 2
 
    S s;
    // set distinguishable field values
    s.b1 = 0b111;
    s.b2 = 0b101111;
    s.b3 = 0b11;
 
    // show layout of fields in S
    auto i = std::bit_cast<std::uint16_t>(s);
    // usually prints 1110000011110111
    // breakdown is:  \_/\/\_/\____/\/
    //                 b1 u a   b2  b3
    // where "u" marks the unused :2 specified in the struct, and
    // "a" marks compiler-added padding to byte-align the next field.
    // Byte-alignment is happening because b2's type is declared unsigned char;
    // if b2 were declared uint16_t there would be no "a", b2 would abut "u".
    for (auto b = i; b; b >>= 1) // print LSB-first
        std::cout << (b & 1);
    std::cout << '\n';
}

I haven't been successful in searching the web for for why if b2 were declared uint16_t then b2 would abut u, and b2 wouldn't be byte-aligned.

Upvotes: 1

Views: 79

Answers (1)

BoP
BoP

Reputation: 3160

Note that the article says "usually" and "possible output".

Some compilers, like the one considered in the example, will just not split bitfields between storage units. So if you store in units of unsigned char, 3 + 2 + 6 bits will not fit. But if you use a 16-bit word, they will all fit.

The standard specifically says:

[Note 1: Bit-fields straddle allocation units on some machines and not on others. Bit-fields are assigned right-to-left on some machines, left-to-right on others. — end note]

This makes them not all that useful if you want any kind of portability. But if you use them for - say - modelling hardware registers, portability is perhaps less of an issue anyway.

Upvotes: 2

Related Questions