Chapter 10: Structures, Unions, and Enumerations
As programs grow, it becomes inefficient to manage data as isolated variables. C provides three related features to group, label, and interpret data efficiently: structures combine fields of different types, unions let multiple representations share the same memory, and enumerations create named integer constants. Together, these constructs make C suitable for modeling complex real-world entities, exchanging binary data, and building readable, maintainable code.
Defining and using structs
A struct (structure) groups variables of different types under a single name. Each element inside a structure is called a member. Structures are essential for representing compound data such as points, employees, files, or records. You define a structure type with the struct keyword and access its members using the dot operator (.).
struct Point {
int x;
int y;
};
int main(void) {
struct Point p1 = {10, 20};
printf("(%d, %d)\n", p1.x, p1.y);
return 0;
}
You can declare variables immediately after defining a structure, or separately later. Although struct tags are distinct from typedefs, they often appear together for brevity.
typedef struct {
char name[32];
int age;
} Person;
Person alice = {"Alice", 28};
Nested structures and arrays of structs
Structures can contain other structures or arrays, enabling layered, hierarchical data. This is common in representing entities with sub-parts, such as a rectangle made of points, or an array of people forming a team. Access nested members using chained dots, or arrows (->) if you work with pointers.
struct Point {
int x;
int y;
};
struct Rectangle {
struct Point top_left;
struct Point bottom_right;
};
struct Rectangle r = {{0, 0}, {10, 10}};
printf("Width: %d\n", r.bottom_right.x - r.top_left.x);
Arrays of structures allow compact data tables that you can loop over easily.
struct Player {
char name[16];
int score;
};
struct Player team[3] = {
{"Alice", 40},
{"Bob", 25},
{"Charlie", 50}
};
for (int i = 0; i < 3; i++) {
printf("%s scored %d\n", team[i].name, team[i].score);
}
Working with unions
A union lets different data types occupy the same memory space. Only one member can hold a valid value at a time. This feature is often used in low-level programming when data may be interpreted in multiple ways, such as converting between integers and byte arrays or implementing tagged variants.
union Number {
int i;
float f;
};
union Number n;
n.i = 1065353216;
printf("%f\n", n.f); // prints 1.000000 (bit reinterpretation)
Because all members share memory, the size of a union equals the size of its largest member. Using the wrong interpretation can lead to undefined behavior, so always track which member is active, typically by keeping a separate indicator variable.
Anonymous and nested unions
Since C11, a union may be declared anonymously inside a structure, allowing its members to be accessed directly without an extra name. Nested unions and structs together form compact, flexible representations of mixed data.
struct Value {
enum {INT, FLOAT} type;
union {
int i;
float f;
};
};
struct Value v = {.type = INT, .i = 42};
printf("%d\n", v.i);
Enumerations for symbolic constants
An enum (enumeration) defines a set of named integer constants that make code clearer and easier to maintain. Enumerations are often used to represent categories, states, or modes. By default, the first name starts at zero and each following name increases by one unless given an explicit value.
enum Direction {
NORTH,
EAST,
SOUTH,
WEST
};
enum Direction dir = EAST;
printf("%d\n", dir); // prints 1
typedef enum to avoid writing enum repeatedly. Combined with meaningful names, enumerations make control logic self-documenting.
You can assign explicit values when needed. Enumerations improve readability and make debugging output more meaningful. Since they are integers at the binary level, you can still use them freely in arithmetic or switch statements.
enum Status {
OK = 0,
WARNING = 1,
ERROR = 2
};