Unions and enums are two essential user-defined types in C that provide memory efficiency and code clarity, respectively.
Union Declaration and Memory Layout
A union groups multiple variables of different types into a single memory location. The compiler allocates enough memory to hold the largest member. All member share the same starting address, meaning writing to one member overwrites the others.
#include <stdio.h>
union Data {
char c;
int i;
};
int main() {
union Data d = {0};
printf("%zu\n", sizeof(d)); // Output: 4 (size of int on most systems)
return 0;
}
Shared Memory Behavior
All members of a union occupy the same memory address:
#include <stdio.h>
union Data {
char c;
int i;
};
int main() {
union Data d;
printf("%p\n", &d); // Base address
printf("%p\n", &d.c); // Same as base
printf("%p\n", &d.i); // Same as base
return 0;
}
Modifying one member affects the interpretation of others:
#include <stdio.h>
union Data {
char c;
int i;
};
int main() {
union Data d;
d.i = 0x11223344;
d.c = 0x55;
printf("%#x\n", d.i); // Output depends on endianness
return 0;
}
Size Calculation Rules for Unions
- The size is at least the size of the largest member.
- The size is rounded up to the nearest multiple of the largest alignment requirement among its members.
#include <stdio.h>
union U1 {
char arr[5]; // 5 bytes
int x; // 4 bytes, aligned to 4
};
union U2 {
short arr[7]; // 14 bytes
int x; // 4 bytes, aligned to 4
};
int main() {
printf("%zu\n", sizeof(union U1)); // 8 (aligned to 4-byte boundary)
printf("%zu\n", sizeof(union U2)); // 16 (14 rounded up to next 4-byte multiple)
return 0;
}
Practical Use: Memory-Efficient Data Structures
Unions help reduce memory usage when only one variant of data is active at a time. For example, in a gift inventory system:
struct Gift {
int stock;
double price;
int type; // Discriminator
union {
struct {
char title[20];
char author[20];
int pages;
} book;
struct {
char design[30];
} mug;
struct {
char design[30];
int colors;
int sizes;
} shirt;
} details;
};
Endianness Detection Using Union
A common trick uses a union to inspect byte order:
int is_little_endian(void) {
union {
int i;
char c;
} u;
u.i = 1;
return u.c == 1; // Returns 1 if little-endian
}
Enum Declaration
Enumerations define a set of named integer constants:
enum Weekday {
MON, TUE, WED, THU, FRI, SAT, SUN
};
enum Color {
RED = 2,
GREEN = 4,
BLUE = 8
};
By default, enumerators start at 0 and increment by 1. Explicit values can be assigned.
Advantages of Enums Over Macros
- Better readability and maintainability
- Type safety (compared to
#define) - Debuggers can display symbolic names
- Scoped to their declaration context
- Define multiple related constants in one statement
Using Enum Variables
enum Color {
RED = 1,
GREEN = 2,
BLUE = 4
};
int main() {
enum Color c = GREEN; // Valid
c = 3; // Allowed in C (not in C++)
return 0;
}
Practice Problems
Problem 1: What is the size of the following union?
union Example {
short s[7]; // 14 bytes
int n; // 4 bytes
};
Answer: 16 bytes (14 rounded up to the next 4-byte boundary due to int alignment).
Problem 2: Predict the output:
#include <stdio.h>
int main() {
union {
short k;
char i[2];
} *ptr, var;
ptr = &var;
ptr->i[0] = 0x39;
ptr->i[1] = 0x38;
printf("%x\n", var.k);
return 0;
}
On a little-endian machine, the output is 3839 because the least significant byte (0x39) is stored at the lower address.