A C union overlays all members in the same storage. Its size is sufficient for its largest member plus alignment, but only the member representing the current value should normally be read. Pair a union with an explicit tag when the program needs a safe variant type.
A union is similar to a struct, but all members share the same memory location. The size of a union equals the size of its largest member. Only one member can hold a valid value at a time.
| Feature | struct | union |
|---|---|---|
| Memory | Each member has its own memory | All members share the same memory |
| Size | Sum of all member sizes (+ padding) | Size of the largest member |
| Active members | All members can be used simultaneously | Only one member is valid at a time |
| Use case | Group related data | Memory-efficient storage of one-of-many types |
union Data {
int i;
float f;
char str[20];
};
union Data d;
d.i = 10; // valid
printf("%d", d.i); // 10
d.f = 3.14f; // now f is valid; i is overwritten
printf("%f", d.f); // 3.14
#include <stdio.h>
struct MyStruct {
int i; // 4 bytes
float f; // 4 bytes
char str[20]; // 20 bytes
}; // total: ~28 bytes
union MyUnion {
int i; // 4 bytes
float f; // 4 bytes
char str[20]; // 20 bytes
}; // total: 20 bytes (size of largest member)
int main() {
printf("Size of struct: %zu bytes\n", sizeof(struct MyStruct)); // ~28
printf("Size of union: %zu bytes\n", sizeof(union MyUnion)); // 20
union MyUnion u;
u.i = 42;
printf("\nu.i = %d\n", u.i);
u.f = 3.14f;
printf("u.f = %.2f\n", u.f);
printf("u.i after setting u.f = %d (corrupted!)\n", u.i); // garbage
// Only the last assigned member is valid
return 0;
}
#include <stdio.h>
#include <string.h>
// Tagged union: struct + union + type tag
typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_STRING } DataType;
typedef struct {
DataType type;
union {
int i;
float f;
char str[50];
} value;
} Variant;
void printVariant(Variant v) {
switch (v.type) {
case TYPE_INT: printf("int: %d\n", v.value.i); break;
case TYPE_FLOAT: printf("float: %.2f\n", v.value.f); break;
case TYPE_STRING: printf("string: %s\n", v.value.str); break;
}
}
int main() {
Variant v1 = {TYPE_INT, .value.i = 42};
Variant v2 = {TYPE_FLOAT, .value.f = 3.14f};
Variant v3;
v3.type = TYPE_STRING;
strcpy(v3.value.str, "Hello, Union!");
printVariant(v1);
printVariant(v2);
printVariant(v3);
return 0;
}
/*
int: 42
float: 3.14
string: Hello, Union!
*/
Define an enum that names the active kind and store it beside the union. Every constructor-like function sets both fields, and every consumer switches on the tag before reading the corresponding member. A default branch can detect corrupted or unsupported tags.
Writing one member and reading another is not a portable general conversion technique; representation and implementation rules matter. Use memcpy for examining object representation when appropriate, and keep ownership rules explicit if a union member contains a pointer.
Explore 500+ free tutorials across 20+ languages and frameworks.