Dataslope logoDataslope

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

Code Block
C 17 (201710L)

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.

Code Block
C 17 (201710L)

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).

Code Block
C 17 (201710L)

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.

Code Block
C 17 (201710L)

Nested structs

Structs compose naturally; the layout rules apply recursively.

Code Block
C 17 (201710L)

Struct pointers and heap structs

Big structs are usually passed by pointer so you do not copy them.

Code Block
C 17 (201710L)

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.

Code Block
C 17 (201710L)

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.

Code Block
C 17 (201710L)

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>.

Code Block
C 17 (201710L)

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.

Code Block
C 17 (201710L)

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.

Code Block
C 17 (201710L)

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

Challenge
C 17 (201710L)
Pack and unpack a 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

Challenge
C 17 (201710L)
Length of a singly 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

QuestionSelect one

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.

QuestionSelect one

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.

QuestionSelect one

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.

On this page