Unions and Enumerations
A union is like a multi-purpose charging cable — you can only use one interface at a time, but it saves space. An enum is like a menu number — using names instead of raw numbers makes code more readable.
union Definition and Shared Memory
All members of a union share the same memory. Its size equals the size of its largest member. Only one member can be used at a time.
union Data {
int i;
float f;
char str[4];
};
printf("%zu\n", sizeof(union Data));
4
After assigning to the int member, reading the float member gives a different interpretation of the same memory:
union Data d;
d.i = 42;
printf("i = %d\n", d.i);
printf("f = %f\n", d.f);
i = 42
f = 0.000000
union vs. struct
| Feature | struct | union |
|---|---|---|
| Memory | Each member has its own space | All members share the same space |
| Size | Sum of all member sizes (with alignment padding) | Size of the largest member |
| Simultaneous access | All members can be accessed at the same time | Only one member at a time |
| Modification | Changing one doesn't affect others | Changing one affects all |
struct SData {
int i;
float f;
char str[4];
};
union UData {
int i;
float f;
char str[4];
};
printf("struct: %zu\n", sizeof(struct SData));
printf("union: %zu\n", sizeof(union UData));
struct: 12
union: 4
Tagged Unions
In practice, unions are typically paired with a tag field that indicates which type is currently stored:
typedef struct {
int type;
union {
int int_val;
float float_val;
char str_val[32];
} value;
} Variant;
void print_variant(const Variant *v) {
switch (v->type) {
case 0: printf("int: %d\n", v->value.int_val); break;
case 1: printf("float: %f\n", v->value.float_val); break;
case 2: printf("str: %s\n", v->value.str_val); break;
}
}
Example
#include <stdio.h>
typedef struct {
int kind;
union {
int ival;
double dval;
} u;
} Number;
void print_number(const Number *n) {
if (n->kind == 0) {
printf("int: %d\n", n->u.ival);
} else {
printf("double: %.2f\n", n->u.dval);
}
}
int main(void) {
Number a = {0, .u.ival = 42};
Number b = {1, .u.dval = 3.14};
print_number(&a);
print_number(&b);
return 0;
}
int: 42
double: 3.14
enum Definition and Usage
Enumerations use names instead of integer constants, improving code readability.
enum Color {
RED,
GREEN,
BLUE
};
enum Color favorite = GREEN;
if (favorite == GREEN) {
printf("Green selected\n");
}
Green selected
Specifying Enum Values
enum Weekday {
MON = 1,
TUE,
WED,
THU,
FRI,
SAT = 100,
SUN
};
printf("MON=%d SAT=%d SUN=%d\n", MON, SAT, SUN);
MON=1 SAT=100 SUN=101
enum with switch
enum Direction { UP, DOWN, LEFT, RIGHT };
void move(enum Direction d) {
switch (d) {
case UP: printf("Move up\n"); break;
case DOWN: printf("Move down\n"); break;
case LEFT: printf("Move left\n"); break;
case RIGHT: printf("Move right\n"); break;
}
}
int — you can assign any integer value, and the compiler may not warn. Don't abuse this feature.
Underlying int Values of Enums
Enum constants are essentially named constants of type int.
enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };
printf("%d %d %d\n", OK, NOT_FOUND, ERROR);
200 404 500
Enum variables can be assigned to and from integers (in C), but it's best to use enum values for type consistency:
enum Status s = OK;
int code = s;
#define for constants: enums have type information, display symbolic names in debuggers, and have more controllable scope.
Using union to Detect Endianness
Endianness refers to the byte order of multi-byte data in memory. A union makes it easy to detect:
int is_little_endian(void) {
union {
int i;
char c;
} u;
u.i = 1;
return u.c == 1;
}
If the lowest byte is stored at the lowest address (u.c == 1), it's little-endian; otherwise it's big-endian.
Example
#include <stdio.h>
typedef union {
unsigned int i;
unsigned char bytes[4];
} EndianTest;
void print_byte_order(void) {
EndianTest t;
t.i = 0x12345678;
printf("Memory byte order: ");
for (int i = 0; i < 4; i++) {
printf("%02X ", t.bytes[i]);
}
printf("\n");
if (t.bytes[0] == 0x78) {
printf("Little Endian\n");
} else {
printf("Big Endian\n");
}
}
int main(void) {
print_byte_order();
return 0;
}
Memory byte order: 78 56 34 12
Little Endian
union for Memory Savings in Practice
When a struct has multiple mutually exclusive fields, use a union to save space:
typedef struct {
int type;
union {
struct { int width, height; } rect;
struct { int radius; } circle;
struct { int side; } square;
} shape;
} Figure;
Figure f;
f.type = 1;
f.shape.circle.radius = 10;
Only one of the three shapes is used at a time. The union shares memory, and the struct size is determined by the largest shape.
❓ FAQ
const char *names[] = {"RED","GREEN","BLUE"}.📖 Summary
- All union members share memory; the size equals the largest member; only one member is used at a time
- In practice, unions are paired with a tag field (tagged union) to distinguish the currently valid type
- enum replaces magic numbers with named constants, improving readability; the underlying type is int
- Enum values default to incrementing from 0; you can manually specify any int value
- A union can detect system endianness — the byte storage order of multi-byte data
📝 Exercises
- Define
union IPAddresscontainingunsigned int addrandunsigned char bytes[4]. Input an IP integer value and output it as both an integer and four separate bytes - Define an enum
enum Season { SPRING, SUMMER, AUTUMN, WINTER }and write a function that returns the corresponding season name string based on the enum value - Use a tagged union to implement a simple shape struct with a type tag and union (rectangle/circle), then write a function to calculate the area



