Structures

A struct groups related values of different types under one name — a book's title, author and year, or a point's x and y. Structures are how C models real-world things, and the building block of every larger data structure.

Defining and using a struct

C
#include <stdio.h>

struct book {
    char title[64];
    char author[48];
    int year;
    double price;
};

int main(void) {
    struct book b = { "The C Programming Language", "Kernighan & Ritchie", 1978, 49.99 };
    printf("%s by %s (%d), %.2f\n", b.title, b.author, b.year, b.price);

    b.price = 39.99;     // members are accessed with a dot
    b.year = 1988;
    printf("Second edition %d now costs %.2f\n", b.year, b.price);
    return 0;
}
Output
The C Programming Language by Kernighan & Ritchie (1978), 49.99
Second edition 1988 now costs 39.99

The definition creates a new type, struct book. Each variable of that type has its own copy of every member, reached with the . operator.

Initialising

C
#include <stdio.h>

struct point {
    double x;
    double y;
};

int main(void) {
    struct point a = { 1.5, -2.0 };              // in member order
    struct point b = { .y = 4.0, .x = 3.0 };     // designated initialisers (clearer)
    struct point origin = { 0 };                 // everything zero
    printf("a(%.1f, %.1f) b(%.1f, %.1f) origin(%.1f, %.1f)\n",
           a.x, a.y, b.x, b.y, origin.x, origin.y);
    return 0;
}
Output
a(1.5, -2.0) b(3.0, 4.0) origin(0.0, 0.0)

Designated initialisers name each member, so they still work if the struct's members are reordered, and any member you leave out is zero.

Copying and assigning

Unlike arrays, structs can be assigned with = — every member is copied, including arrays inside the struct:

C
#include <stdio.h>
#include <string.h>

struct player {
    char name[16];
    int score;
};

int main(void) {
    struct player p1 = { "Ada", 120 };
    struct player p2 = p1;            // full copy
    strcpy(p2.name, "Alan");
    p2.score = 95;
    printf("%s %d | %s %d\n", p1.name, p1.score, p2.name, p2.score);
    return 0;
}
Output
Ada 120 | Alan 95

Structs can't be compared with ==, though — compare the members you care about.

Structs and functions

Structs can be passed to and returned from functions. They're passed by value — the function gets a copy:

C
#include <stdio.h>
#include <math.h>

struct point { double x, y; };

double distance(struct point a, struct point b) {
    return hypot(b.x - a.x, b.y - a.y);
}

struct point midpoint(struct point a, struct point b) {
    return (struct point) { (a.x + b.x) / 2, (a.y + b.y) / 2 };
}

int main(void) {
    struct point home = { 0, 0 }, shop = { 3, 4 };
    struct point mid = midpoint(home, shop);
    printf("distance %.1f, midpoint (%.1f, %.1f)\n", distance(home, shop), mid.x, mid.y);
    return 0;
}
Output
distance 5.0, midpoint (1.5, 2.0)

(struct point) { … } is a compound literal: a temporary struct value written in place. For large structs, or to modify the caller's struct, pass a pointer instead (next lesson).

Arrays of structs

C
#include <stdio.h>

struct product {
    const char *name;
    int stock;
    double price;
};

int main(void) {
    struct product inventory[] = {
        { "Notebook", 40, 3.50 },
        { "Pen", 150, 1.20 },
        { "Backpack", 6, 39.90 },
    };
    size_t n = sizeof inventory / sizeof inventory[0];
    double value = 0;
    for (size_t i = 0; i < n; i++) {
        printf("%-10s %4d x %6.2f\n", inventory[i].name, inventory[i].stock, inventory[i].price);
        value += inventory[i].stock * inventory[i].price;
    }
    printf("Stock value: %.2f\n", value);
    return 0;
}
Output
Notebook     40 x   3.50
Pen         150 x   1.20
Backpack      6 x  39.90
Stock value: 559.40

Nested structs

C
#include <stdio.h>

struct date { int year, month, day; };

struct employee {
    char name[32];
    struct date hired;
    double salary;
};

int main(void) {
    struct employee e = { "Grace", { 2019, 3, 14 }, 52000 };
    printf("%s was hired on %04d-%02d-%02d\n", e.name, e.hired.year, e.hired.month, e.hired.day);
    return 0;
}
Output
Grace was hired on 2019-03-14

Size and padding

A struct may be larger than the sum of its members: the compiler inserts invisible padding so that each member sits at an address suitable for its type. Ordering members from largest to smallest often reduces it:

C
#include <stdio.h>

struct loose { char a; double b; char c; };
struct tight { double b; char a; char c; };

int main(void) {
    printf("loose: %zu bytes, tight: %zu bytes\n", sizeof(struct loose), sizeof(struct tight));
    return 0;
}
Output
loose: 24 bytes, tight: 16 bytes

That's also why you should never assume a struct's size — always use sizeof.

Try it yourself

Store several students with their scores, then find and print the top student.

Try it yourself · main.c
Edit the code, copy it into main.c, then compile and run it with cc -std=c17 -Wall main.c -o main && ./main. How to set up a C compiler
Output
Top student: Liam with 94