A union is a special data type where all members share the
same memory location. At any given time, only one member can hold a
value. The union takes up only as much space as its largest member.
This is useful for memory-efficient data representations and
low-level programming.
Union Basics
All members of a union overlap in memory. Writing to one member overwrites the others. The size of the union is the size of its largest member:
#include <iostream>
union Data {
int i;
float f;
char c;
};
int main() {
Data d;
d.i = 42;
std::cout << "As int: " << d.i << "\n";
d.f = 3.14f;
std::cout << "As float: " << d.f << "\n";
std::cout << "Int is now corrupted: " << d.i << "\n";
d.c = 'A';
std::cout << "As char: " << d.c << "\n";
std::cout << "Size of union: " << sizeof(d) << " bytes\n";
return 0;
}
When we set d.f = 3.14f, the integer value is overwritten.
The union size is 4 bytes (the size of int and
float on most systems) because char is only 1
byte.
Size in Memory
The union size equals its largest member. Compare this to a struct, which would be the sum of all its members (plus padding). Unions save memory when you need to store one of several possible types:
#include <iostream>
union Variant {
int i;
double d;
char str[32];
};
struct VariantStruct {
int i;
double d;
char str[32];
};
int main() {
std::cout << "Union size: " << sizeof(Variant) << " bytes\n";
std::cout << "Struct size: " << sizeof(VariantStruct) << " bytes\n";
return 0;
}
The union holds the largest member — the char[32] — so it
takes 32 bytes. The struct would take at least 41 bytes (4 + 8 + 32)
plus padding. That is a significant saving.
Unions with Discriminators
The problem with unions is that you never know which member is currently active. The common solution is to pair the union with a separate variable (discriminator) that tracks the active type:
#include <iostream>
enum ValueType { VAL_INT, VAL_DOUBLE, VAL_TEXT };
struct TaggedValue {
ValueType type;
union {
int i;
double d;
char text[20];
};
};
int main() {
TaggedValue tv;
tv.type = VAL_INT;
tv.i = 42;
if (tv.type == VAL_INT) {
std::cout << "Integer: " << tv.i << "\n";
}
tv.type = VAL_DOUBLE;
tv.d = 2.718;
if (tv.type == VAL_DOUBLE) {
std::cout << "Double: " << tv.d << "\n";
}
return 0;
}
This pattern is called a tagged union or discriminated union. Always check the discriminator before reading the union value — otherwise you will interpret raw bytes as the wrong type.