Вы сказали это сами:
Я знаю, что компилятор может упаковать битовые поля внутри "единиц выделения", и даже может вставить заполнение , ...
Именно так и происходит.
uint8_t
содержит 8 бит. Первые два поля в вашей структуре, coarseX
и coarseY
, имеющие по 5 битов, не могут помещаться последовательно в пределах одного байта в памяти. Компилятор сохраняет coarseX
в 1-м байте, а затем должен записать sh coarseY
во 2-й байт в памяти, оставив в памяти 3 неиспользуемых бита между coarseX
и coarseY
, которые смещают ваши значения в регистре .
Следующие 3 поля, coarseY
, baseNametableAddressX
и baseNametableAddressY
, всего 7 битов, поэтому они вписываются в этот 2-й байт.
Но этот байт не может содержать Поля fineY
и unused
, поэтому они помещаются в 3-й байт в памяти, оставляя в памяти 1 неиспользуемый бит между baseNametableAddressY
и fineY
, которые смещают ваши значения в регистре. И регистр не может получить доступ к этому 3-му байту!
Итак, по сути, ваш struct
в конечном итоге ведет себя так, как если бы вы объявили это так:
union
{
struct
{
// byte 1
uint8_t coarseX : 5;
uint8_t padding1 : 3;
// byte 2
uint8_t coarseY : 5;
uint8_t baseNametableAddressX : 1;
uint8_t baseNametableAddressY : 1;
uint8_t padding2 : 1;
// byte 3!
uint8_t fineY : 3;
uint8_t unused : 1;
uint8_t padding3 : 4;
} bits;
struct {
uint16_t reg; // <-- 2 bytes!
uint8_t padding4; // <-- !
}
} addressT, addressV; // temporary
Используя uint16_t
вместо uint8_t
, вы не столкнитесь с этой проблемой, добавив доплату, поскольку для регистра достаточно битов, выделенных для хранения всех битов, которые вы определяете.