What Is a Union? โ The Formal Definition
A union is a user-defined data type, declared with the same syntax as a struct, whose members all share the exact same block of memory instead of each getting their own. At any given moment, a union holds a meaningful value for only one of its members โ writing to one member overwrites whatever bytes were stored for any other member.
Contrast this with a structure: every member of a struct gets its own separate memory, laid out one after another, and all members remain simultaneously valid and independently readable.
| Property | struct | union |
|---|---|---|
| Memory per member | Each member gets its own space | All members share one space |
| Total size | Sum of all members (plus padding) | Size of the largest member only |
| Valid members at once | All members, simultaneously | Only one member at a time |
| Writing one member | Leaves all other members unaffected | Overwrites the bytes of every other member |
| Typical use | Grouping related, independent fields (a Student's roll, name, marks โ all needed together) | Representing "one value that could be one of several types" (a Value that's either an int OR a float, never both) |
The syntax is deliberately almost identical to a struct โ this is intentional, since the C designers wanted the two features to feel like close relatives:
struct Point { // STRUCT โ x and y both exist at once int x; int y; }; union Data { // UNION โ only one of these is meaningful at a time int i; float f; char c; };
Memory Layout โ Seeing the Difference Visually
Take the exact same three members โ int i, float f, char c โ and place them in a struct versus a union. This is where the real difference becomes impossible to miss.
This single picture explains almost everything unusual about unions: because i, f, and c
Example 1 โ Proving the Overlap (sizeof & Overwrite Demo)
This example declares a struct and a union with identical members, prints both sizes to confirm the theory above, then demonstrates the union's defining behavior: writing to one member destroys the value previously stored in another.
#include <stdio.h> struct DataStruct { int i; float f; char c; }; union DataUnion { int i; float f; char c; }; int main() { struct DataStruct s; union DataUnion u; printf("sizeof(struct DataStruct) = %zu bytes\n", sizeof(s)); printf("sizeof(union DataUnion) = %zu bytes\n\n", sizeof(u)); // --- In the struct, all three fields hold independent values --- s.i = 65; s.f = 3.14; s.c = 'Z'; printf("struct -> i=%d, f=%.2f, c=%c (all three remain valid)\n\n", s.i, s.f, s.c); // --- In the union, writing i overwrites whatever was in f and c --- u.i = 65; printf("After u.i = 65 -> u.i = %d\n", u.i); u.f = 3.14; // this OVERWRITES the same 4 bytes u.i was using printf("After u.f = 3.14 -> u.f = %.2f, but u.i is now garbage: %d\n", u.f, u.i); u.c = 'Z'; // this overwrites the first byte of whatever u.f held printf("After u.c = 'Z' -> u.c = %c, but u.f is now garbage: %f\n", u.c, u.f); return 0; }
sizeof(struct DataStruct) = 12 bytes sizeof(union DataUnion) = 4 bytes struct -> i=65, f=3.14, c=Z (all three remain valid) After u.i = 65 -> u.i = 65 After u.f = 3.14 -> u.f = 3.14, but u.i is now garbage: 1078523331 After u.c = 'Z' -> u.c = Z, but u.f is now garbage: 3.209386
Example 2 โ The Tagged Union (Real-World Pattern)
Raw unions are dangerous alone โ nothing stops you from writing an int and reading it back as a float by mistake. The fix, used constantly in real compilers, interpreters, and network protocol code, is the tagged union: pair the union with an extra field (the "tag") that records which member is currently valid.
#include <stdio.h> // The tag: an enum recording which member is currently valid typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_STRING } ValueType; // The union itself: only ONE of these three is meaningful at a time typedef union { int asInt; float asFloat; char asString[20]; } ValueData; // The tagged union: pairs the tag with the data so we always know which member to read typedef struct { ValueType type; // which member is valid right now ValueData data; // the actual shared storage } Value; void printValue(Value v) { switch (v.type) { // the tag tells us EXACTLY which member to read case TYPE_INT: printf("Integer: %d\n", v.data.asInt); break; case TYPE_FLOAT: printf("Float: %.2f\n", v.data.asFloat); break; case TYPE_STRING: printf("String: %s\n", v.data.asString); break; } } int main() { Value values[3]; values[0].type = TYPE_INT; values[0].data.asInt = 42; values[1].type = TYPE_FLOAT; values[1].data.asFloat = 3.14159; values[2].type = TYPE_STRING; for (int i = 0; values[2].data.asString[i] != '\0' && i < 19; i++); // (see note below) printf("A mixed list of 3 self-describing values:\n"); printValue(values[0]); printValue(values[1]); // simpler and correct way to set the string member: sprintf(values[2].data.asString, "Hello"); printValue(values[2]); printf("\nsizeof(Value) = %zu bytes (tag + largest member, not the sum of all 3)\n", sizeof(Value)); return 0; }
A mixed list of 3 self-describing values: Integer: 42 Float: 3.14 String: Hello sizeof(Value) = 28 bytes (tag + largest member, not the sum of all 3)
When to Reach for a struct vs a union
The deciding question is always the same: do all these pieces of data need to exist and be readable at the same time, or is it really just one value that happens to come in different possible shapes?
| Scenario | Use | Why |
|---|---|---|
| A student's roll number, name, and marks | struct | All three fields are needed together, at all times |
| A configuration value that's either an int, float, or string | union (tagged) | Only ever one type at a time โ no need for three separate fields sitting mostly unused |
| A network packet header interpreted differently by protocol type | union | Same raw bytes, different meaning depending on context |
| An RGB color's red, green, and blue components | struct | All three components are always needed simultaneously to describe one color |
| Memory-constrained embedded firmware storing one of several sensor readings | union | Saves memory โ only one sensor's data needs storage at any moment |
Quick Quiz
What is the size of a union, compared to its members?
In a union with int i and float f, what happens if you set u.i = 10 and then u.f = 2.5?
What is a "tagged union"?
Which scenario is a better fit for a struct than a union?
Why doesn't a union "warn" you when you read the wrong member?
Lesson Checklist
- I can define a union in my own words, contrasted against a struct
- I understand why sizeof(union) equals its largest member's size
- I can explain why writing one union member corrupts the others
- I can declare and use a union with multiple member types
- I understand what a tagged union is and why it's used
- I can build a simple tagged union with an enum + union + struct
- I can decide when a scenario calls for a struct vs a union
- I completed the quiz