Unions
Several members sharing one piece of memory.
6 minute read
Big idea
A union looks like a struct, but all its members share the same memory. It holds one of them at a time — and it's only as big as its largest member.
The idea
A struct gives every member its own space. A union gives them all one space:
union Number {
int i;
float f;
};
union Number can hold an int or a float — not both. Writing one member overwrites the other, because they live at the same address.
#include <stdio.h>
struct Both { int i; double d; char c; };
union OneOf { int i; double d; char c; };
int main(void) {
printf("struct: %zu bytes\n", sizeof(struct Both));
printf("union: %zu bytes\n", sizeof(union OneOf));
return 0;
}Run and edit it in the app.
The struct needs room for all three members (plus some padding). The union only needs room for the biggest one — the double, 8 bytes.
Watch it
(This picture assumes a little-endian machine — the low byte stored first — which is what nearly every laptop and phone uses.)
The useful pattern: a tagged union
On its own a union doesn't remember which member you last wrote. So we pair it with an enum "tag" inside a struct — the tag says which member is valid:
#include <stdio.h>
#include <string.h>
typedef enum { KIND_INT, KIND_FLOAT, KIND_TEXT } Kind;
typedef struct {
Kind kind; // which member is valid right now
union {
int i;
float f;
char text[16];
} as;
} Value;
void print_value(Value v) {
switch (v.kind) {
case KIND_INT: printf("int %d\n", v.as.i); break;
case KIND_FLOAT: printf("float %.2f\n", v.as.f); break;
case KIND_TEXT: printf("text \"%s\"\n", v.as.text); break;
}
}
int main(void) {
Value a = { .kind = KIND_INT, .as.i = 42 };
Value b = { .kind = KIND_FLOAT, .as.f = 2.5f };
Value c = { .kind = KIND_TEXT };
strcpy(c.as.text, "hello");
print_value(a);
print_value(b);
print_value(c);
return 0;
}output
int 42
float 2.50
text "hello"Run and edit it in the app.
.kind = KIND_INT is a designated initializer — it sets a member by name. Every Value is the same size, yet it can carry three different kinds of data. Interpreters, JSON parsers, and network message formats are full of this pattern.
union U { char c; int i; double d; }; On a machine where int is 4 bytes and double is 8, what is sizeof(union U) most likely to be?
Watch the tag and the slot
Common mistake
Reading a member you didn't last write, expecting a sensible value. Write f = 2.5f, then read i, and you get the float's raw bit pattern as an int — a meaningless number. Always keep a tag, and read only the member the tag says is valid.
Think of it like
One parking space with a sign that says what's parked in it. It can fit a car or a motorbike, never both — and if you don't read the sign, you might go looking for a car that isn't there.
Key terms
- Union (
union) — A type whose members all share the same memory. - Tagged union — A struct holding a union plus an enum saying which member is valid.
- Designated initializer — Setting members by name:
{ .kind = KIND_INT, .as.i = 42 }.
Check yourself
How do a union's members relate to each other?
How big is a union?
In a tagged union, what is the tag for?