Structs, unions and bit-fields
A struct groups fields, and the compiler adds padding so each field sits at an aligned address: member order changes the size. A union overlays its members on the same bytes. Bit-field layout is implementation-defined, so registers are usually described with structs of fixed-width volatile members and masks.
Padding and alignment
Most processors read a 4-byte value fastest, or only, at an address divisible by 4. So the compiler aligns each member and inserts padding: in struct loose below, three padding bytes follow flag so that count starts at offset 4, and more pad the end so that arrays of the struct stay aligned. Putting the widest members first, as in struct tight, saves 4 bytes per instance. offsetof from <stddef.h> reports where a member really sits.
#include <stddef.h>#include <stdint.h>#include <stdio.h>struct loose { uint8_t flag; uint32_t count; uint8_t mode;};struct tight { uint32_t count; uint8_t flag; uint8_t mode;};union word { uint32_t value; uint8_t bytes[4];};int main(void) { printf("loose: %zu bytes, count at offset %zu\n", sizeof(struct loose), offsetof(struct loose, count)); printf("tight: %zu bytes\n", sizeof(struct tight)); union word w = { .value = 0x11223344u }; printf("first byte: 0x%02X\n", (unsigned)w.bytes[0]); return 0;}
loose: 12 bytes, count at offset 4 tight: 8 bytes first byte: 0x44
The sizes above are typical of 32- and 64-bit targets. A union stores all its members in the same bytes, so writing value and reading bytes[0] shows the byte order: 0x44 on a little-endian machine.
Bit-fields and register overlays
A bit-field such as unsigned mode : 3; packs a field into a few bits, which looks ideal for registers. But the order of the bits and whether a field may span two storage units are implementation-defined, and the standard says nothing about the width of each access, so the same declaration can map differently between compilers. Register code therefore usually describes a peripheral as a struct of fixed-width volatile members, in address order, placed at the base address, and uses masks for the bits.
#include <stdint.h>typedef struct { volatile uint32_t CTRL; /* offset 0x00 */ volatile uint32_t STATUS; /* offset 0x04 */ volatile uint32_t DATA; /* offset 0x08 */} uart_regs;/* Example base address: take the real one from your reference manual. */#define UART0 ((uart_regs *)0x40001000u)void uart_send(uint8_t byte) { while ((UART0->STATUS & 1u) == 0u) { /* wait until the transmitter is ready */ } UART0->DATA = byte;}