Code of the Day
IntermediateStructs and enums

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.

CIntermediate10 min read
By the end of this lesson you will be able to:
  • 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.

Finished reading? Mark it complete to track your progress.

On this page