Unions & Enums
Shared memory and named numbersA union looks like a struct, but all its members live in the same memory — so it can hold one of them at a time. An enum gives readable names to integer constants. In the lab below you watch three members read the same four bytes, drive a switch with enum names, and combine both into a tagged union.
- unionall members share the same bytes
- sizeofof a union = its largest member
- enumnames for int constants
Ready
Keyboard: ← → step · Space play / pause · R reset · Esc exit classroom mode
The ideas behind the lab
Exactly what you watched happen, in plain steps.
Shared memory, named numbers
A union is declared like a struct, but every member starts at the same address: the union is only as big as its largest member and holds one member at a time. Writing one member overwrites the others, so a program must remember which member is valid — often with an enum "tag". An enum defines named int constants (0, 1, 2 … unless you give values), which makes code with fixed choices readable.
union Value { int i; float f; char c; }; // 4 bytes in total
union Value v;
v.i = 65; // v.c now reads 'A' (same first byte)
v.f = 1.5f; // overwrites i
enum Color { RED, GREEN, BLUE }; // 0, 1, 2
enum Level { LOW, MEDIUM = 5, HIGH }; // 0, 5, 6
enum Color c = GREEN;
| struct | union | |
|---|---|---|
| Member addresses | one after another | all the same |
| Size | 12 (4 + 4 + 1 + padding) | 4 (the largest member) |
| Members valid at once | all of them | only the last one written |
| Writing one member | leaves the others alone | changes what the others read |
| Typical use | a record with several fields | a value that is one of several types |
Unions
- union Name { members }; — same syntax as a struct.
- All members share memory; sizeof is the largest member (plus padding).
- Write one member, then read that same member.
- Reading another member gives the raw bytes reinterpreted — usually nonsense.
- Unions save memory when only one of several values is needed at a time.
Enums
- enum Name { A, B, C }; — A = 0, B = 1, C = 2.
- You may set values: { LOW = 1, HIGH = 10 }; the next name continues from the last value.
- Enum constants are ints: printf("%d", B) prints 1.
- They are ideal for switch cases and for array indexes (names[MON]).
- Prefer enums to many #defines: the names belong together and debuggers show them.
Tagged unions
- Put an enum "kind" and a union inside one struct.
- The kind says which union member is in use.
- Always set the kind together with the member.
- Read with switch (s.kind) and use only the matching member.
- This is how many programs store "a number OR a text OR …".
Weekdays with an enum, shapes with a tagged union
Every line, explained.
#include <stdio.h>
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN };
0 to 6.
const char *names[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
typedef enum { CIRCLE, RECT } ShapeKind;
typedef struct {
ShapeKind kind;
The tag.
union {
double radius;
Used when kind == CIRCLE.
struct { double w, h; } rect;
Used when kind == RECT.
} u;
} Shape;
double area(Shape s)
{
switch (s.kind) {
The tag decides which member to read.
case CIRCLE: return 3.14159 * s.u.radius * s.u.radius;
case RECT: return s.u.rect.w * s.u.rect.h;
}
return 0;
}
int main(void)
{
for (enum Day d = MON; d <= SUN; d++)
An enum used as a loop counter and an index.
if (d >= SAT) printf("%s is weekend\n", names[d]);
Sat is weekend / Sun is weekend
Shape a = { CIRCLE, { .radius = 1.0 } };
Shape b = { RECT };
b.u.rect.w = 2; b.u.rect.h = 3;
printf("%.2f %.2f\n", area(a), area(b));
Prints 3.14 6.00
printf("%zu\n", sizeof(Shape));
4 + 4 padding + 16 (the bigger union member) = 24
return 0;
}
Spot the bug
Mistakes almost every beginner makes — and how to fix them.
Reading the wrong union member
v.f = 1.5f;
printf("%d", v.i); // 1069547520
v.f = 1.5f;
printf("%f", v.f);
Only the member written last holds a meaningful value.
Expecting all union members to keep their values
u.n = 42;
u.c = 'Z';
printf("%d", u.n); // no longer 42
// use a struct if you need both values at the same time
All members share the same bytes; writing c changed part of n.
Forgetting the tag of a tagged union
s.u.radius = 2.0; // kind still RECT
s.kind = CIRCLE; s.u.radius = 2.0;
Code that trusts the tag would read rect.w from a circle.
Thinking enum values are strings
printf("%s", GREEN); // crash
printf("%d", GREEN); // 1
printf("%s", colorNames[GREEN]); // "Green"
An enum constant is just an int. Keep a separate array of names to print them.
Two enums with the same constant name
enum Light { RED, GREEN };
enum Color { RED, BLUE }; // error: RED redeclared
enum Light { LIGHT_RED, LIGHT_GREEN };
enum Color { COLOR_RED, COLOR_BLUE };
All enum constants share one name space. Prefix them.
Check your understanding
Answer each question — you will see the explanation straight away.
union U { int i; double d; char c; }; — what is sizeof(union U) on a typical system?
The size of the largest member, double.
enum Color { RED, GREEN, BLUE }; — what is the value of BLUE?
Counting starts at 0.
enum E { A = 3, B, C = 10, D }; — what are B and D?
Each name without a value is the previous one + 1.
union V { int i; char c; } v; v.i = 66; — what does v.c read (little-endian)?
c reads the lowest byte of i: 66 is 'B'.
Why does a tagged union need the enum "kind"?
The union itself does not know which member was written last.
Unions & Enums quiz
Test yourself: 10 questions, 70% to pass. Create a free account to take the quiz, save your progress and work towards the certificate.