Nested structs and arrays of structs
Compose complex types from simpler structs — nested struct members, arrays of structs, and how to work with them efficiently.
- Declare a struct that contains another struct as a member
- Access nested struct members using chained dot or arrow notation
- Create and iterate over an array of structs
- Explain how arrays of structs are laid out in memory
Real programs have data with hierarchical structure. A game has players, each with a position, a score, and a health level. A database table has rows, each with multiple columns of different types. C structs compose naturally — a struct member can itself be a struct.
Nested structs
#include <stdio.h>
struct Point {
double x;
double y;
};
struct Circle {
struct Point center; /* a Point embedded in Circle */
double radius;
};Access nested members by chaining the dot operator:
struct Circle c;
c.center.x = 3.0;
c.center.y = 4.0;
c.radius = 5.0;
printf("Center: (%.1f, %.1f)\n", c.center.x, c.center.y);Initialisation with nested designated initialisers:
struct Circle c = {
.center = { .x = 3.0, .y = 4.0 },
.radius = 5.0
};Arrays of structs
An array of structs stores multiple instances contiguously in memory:
#include <stdio.h>
struct Student {
char name[32];
int grade;
double gpa;
};
int main(void) {
struct Student class[] = {
{ .name = "Alice", .grade = 11, .gpa = 3.8 },
{ .name = "Bob", .grade = 10, .gpa = 3.2 },
{ .name = "Carol", .grade = 12, .gpa = 3.9 },
};
int n = 3;
for (int i = 0; i < n; i++) {
printf("%-10s grade %d GPA %.1f\n",
class[i].name, class[i].grade, class[i].gpa);
}
return 0;
}Array elements are accessed with class[i].member — index then dot.
Memory layout of arrays of structs
Each struct is placed in memory back-to-back. With sizeof(struct Student) = 40 bytes (32 + 4 + 8, ignoring padding):
class[0]: bytes 0–39 (Alice)
class[1]: bytes 40–79 (Bob)
class[2]: bytes 80–119 (Carol)This contiguous layout is very cache-friendly — iterating over the array accesses memory sequentially.
Compare with an "array of pointers to structs" (common in C programs):
struct Student *roster[3];
roster[0] = malloc(sizeof(struct Student));
/* ... */Here, each struct may be anywhere in the heap. Iterating requires pointer chasing, which is slower due to cache misses.
Finding the highest GPA
int max_idx = 0;
for (int i = 1; i < n; i++) {
if (class[i].gpa > class[max_idx].gpa) {
max_idx = i;
}
}
printf("Top student: %s (%.1f)\n", class[max_idx].name, class[max_idx].gpa);Pointers into arrays of structs
When you have a pointer to an element, use -> to access its members:
struct Student *top = &class[max_idx];
printf("Top: %s\n", top->name); /* -> instead of . */
top->gpa = 4.0; /* modifies the original in the array */Passing an array of structs to a function
void print_roster(const struct Student *students, int n) {
for (int i = 0; i < n; i++) {
printf("%s: %.1f\n", students[i].name, students[i].gpa);
}
}
/* Call: */
print_roster(class, n);The array decays to a pointer to its first element, as always. const struct Student * means "pointer to const Student" — the function promises not to modify the students.
Struct of arrays vs. array of structs. For performance-critical code (game engines, numerical computing), the layout matters. An "array of structs" (AoS) is easy to write but may be slow for SIMD operations. A "struct of arrays" (SoA) — separate arrays for each field — allows the CPU to process many values of the same field at once. This is an advanced optimisation topic worth knowing exists.
Where to go next
Next: pointers to structs — using ->, dynamic allocation of structs, and the patterns needed for linked lists and trees.
Defining and using structs
Group related data into custom types with C structs — declaration, member access, initialization, and passing structs to functions.
Pointers to structs
Work with pointers to structs in C — the arrow operator, dynamic struct allocation, self-referential structs for linked lists, and function parameters.