Structs, Unions, and Padding
Defining composite types, struct layout, padding, alignment, bitfields, and unions
A struct is C's way of bundling several values into one named
record. Underneath the syntax, a struct is just a block of bytes —
its members live one after another in memory, possibly with padding
bytes inserted to keep each member at a properly aligned address.
This page is about both the language feature and the byte-level reality.
Defining and using a struct
The -> operator is just shorthand for "dereference then dot."
typedef to drop the struct keyword
Most modern C codebases typedef their structs so callers can write
Point instead of struct Point.
Memory layout and padding
Members of a struct are laid out in declaration order, but the compiler may insert padding bytes so each member starts at an address that satisfies its alignment requirement. The struct itself is also padded to a multiple of its strictest member's alignment, so that arrays of the struct stay aligned.
The int i must sit at a 4-byte-aligned offset, so the compiler
slips three padding bytes after c. After d, three more padding
bytes round the struct up to 12 bytes (so that Bad bads[10] keeps
every i aligned).
Layout tip: order members from largest to smallest
Sorting fields by descending alignment (largest types first) usually minimizes padding. `Good` above is 8 bytes; `Bad` is 12.
offsetof and pointer arithmetic on members
offsetof(type, member) from <stddef.h> returns the byte offset of
a member inside a struct. It is widely used in kernels, networking
code, and any "container of" macro.
Nested structs
Structs compose naturally; the layout rules apply recursively.
Struct pointers and heap structs
Big structs are usually passed by pointer so you do not copy them.
The pair user_new / user_free is the standard C "constructor /
destructor" pattern — they are just regular functions, but they
document the ownership rule clearly.
Linked list node — a self-referential struct
A struct can contain a pointer to its own type. This is how linked lists, trees, and graphs are built.
Unions
A union is a value that can hold any one of several types — but
only one at a time. Every member shares the same storage; the
union's size is the size of its largest member.
This type punning pattern is common in low-level code (graphics,
serialization). A "tagged union" — a struct with an enum tag and a
union of payloads — is how C encodes sum types like Result<Ok, Err>.
Bitfields
Sometimes you need to pack several small fields into a single integer — say, flags on a packet header or per-pixel data. C's bitfield syntax lets you declare members in widths smaller than a byte.
Bitfields are not portable for wire formats
The order in which bitfields are packed into the underlying integer — and the integer's size — is implementation-defined. They are great for in-memory flags but a poor choice for cross-platform binary formats; use explicit bit masks for those.
Flexible array members
A struct may end with a "flexible array member" — an array of
unspecified size that lets you allocate the struct and its data in
one malloc.
One allocation, one free, contiguous storage — friendlier to the
allocator and the CPU cache than Buffer { size_t len; char *data; }
where data is its own allocation.
Practice: pack RGBA bytes into a 32-bit pixel
Implement uint32_t pack_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) so the bytes are laid out with r in the highest byte and a in the lowest byte. The provided main prints the result in hex; pack_rgba(0xAA, 0xBB, 0xCC, 0xDD) must print AABBCCDD.
Practice: count the nodes in a linked list
Implement size_t list_length(const Node *head) that returns the number of nodes in the list. The provided main builds a 5-node list and prints the length on one line. Do not free the list — main handles that.
Test your understanding
Why does the struct struct Bad { char c; int i; char d; }; have size 12 on a 4-byte-aligned target instead of the obvious 6?
The compiler inserts random bytes for security.
The compiler inserts padding so each member sits at a properly aligned offset and so arrays of the struct keep their members aligned.
The struct must always be a multiple of 12 bytes by language rule.
char is actually 4 bytes on most platforms.
What is the key property of a union?
All members live at consecutive offsets, like a struct.
The union always uses the sum of its members' sizes.
All members share the same storage; the union holds at most one of them at a time, and its size is the size of the largest member (plus padding).
Unions are always initialized to zero on declaration.
Which pattern lets you store a variable-length payload inline with a struct using a single allocation?
A void *data member that you malloc separately.
A nested struct.
A flexible array member declared as the last field, e.g. char data[];, allocated via malloc(sizeof *s + n).
A bitfield.