Structs and Pointers

Most real C code handles structs through pointers: to let functions modify them, to avoid copying large structs, and to allocate them on the heap. The -> operator makes this convenient, and opaque pointers let you hide a struct's details completely.

The arrow operator

With a pointer to a struct, p->member is shorthand for (*p).member:

C
#include <stdio.h>

struct counter {
    const char *label;
    int value;
};

void increment(struct counter *c) {
    c->value++;               // same as (*c).value++
}

int main(void) {
    struct counter visits = { "visits", 0 };
    for (int i = 0; i < 3; i++) increment(&visits);
    printf("%s: %d\n", visits.label, visits.value);
    return 0;
}
Output
visits: 3

Use . with a struct variable and -> with a pointer to one.

Const pointers for reading

Passing a pointer instead of a copy saves time for large structs. Mark it const when the function only reads:

C
#include <stdio.h>

struct account {
    char owner[32];
    char iban[34];
    long balance_cents;
    long transactions[100];
};

void print_summary(const struct account *a) {
    printf("%s: %ld.%02ld\n", a->owner, a->balance_cents / 100, a->balance_cents % 100);
}

int main(void) {
    struct account acc = { .owner = "Sam Rivera", .balance_cents = 125075 };
    printf("struct size: %zu bytes, pointer size: %zu bytes\n", sizeof acc, sizeof &acc);
    print_summary(&acc);
    return 0;
}
Output
struct size: 880 bytes, pointer size: 8 bytes
Sam Rivera: 1250.75

Structs on the heap

A function that creates a struct with malloc and returns a pointer is a common pattern — often paired with a matching "destroy" function:

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

struct playlist {
    char *name;
    int track_count;
};

struct playlist *playlist_create(const char *name) {
    struct playlist *p = malloc(sizeof *p);
    if (p == NULL) return NULL;
    p->name = malloc(strlen(name) + 1);
    if (p->name == NULL) {
        free(p);
        return NULL;
    }
    strcpy(p->name, name);
    p->track_count = 0;
    return p;
}

void playlist_destroy(struct playlist *p) {
    if (p == NULL) return;
    free(p->name);       // free what the struct owns first...
    free(p);             // ...then the struct itself
}

int main(void) {
    struct playlist *mix = playlist_create("Road trip");
    if (mix == NULL) return 1;
    mix->track_count = 12;
    printf("%s has %d tracks\n", mix->name, mix->track_count);
    playlist_destroy(mix);
    return 0;
}
Output
Road trip has 12 tracks

Self-referential structs

A struct can't contain itself, but it can contain a pointer to its own type. That's the basis of linked lists and trees (lesson 45):

C
#include <stdio.h>

struct person {
    const char *name;
    struct person *manager;    // pointer to the same struct type
};

int main(void) {
    struct person ceo = { "Ines", NULL };
    struct person lead = { "Tom", &ceo };
    struct person dev = { "Priya", &lead };

    for (const struct person *p = &dev; p != NULL; p = p->manager) {
        printf("%s%s", p->name, p->manager ? " reports to " : "\n");
    }
    return 0;
}
Output
Priya reports to Tom reports to Ines

Opaque types

A header can declare a struct without revealing its members — an incomplete type. Code elsewhere can only use pointers to it and the functions you provide, so you can change the struct's internals without breaking anyone. FILE from <stdio.h> works this way.

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

// --- what a header would show ---
typedef struct stack Stack;           // members hidden
Stack *stack_new(void);
void stack_push(Stack *s, int v);
int stack_pop(Stack *s);
void stack_free(Stack *s);

// --- what only the implementation file sees ---
struct stack { int items[16]; int top; };

Stack *stack_new(void) { return calloc(1, sizeof(Stack)); }
void stack_push(Stack *s, int v) { if (s->top < 16) s->items[s->top++] = v; }
int stack_pop(Stack *s) { return s->top > 0 ? s->items[--s->top] : -1; }
void stack_free(Stack *s) { free(s); }

int main(void) {
    Stack *s = stack_new();
    if (s == NULL) return 1;
    stack_push(s, 1);
    stack_push(s, 2);
    stack_push(s, 3);
    int a = stack_pop(s);
    int b = stack_pop(s);
    printf("%d %d\n", a, b);
    stack_free(s);
    return 0;
}
Output
3 2

In a real project the two parts live in stack.h and stack.c (lesson 52).

Try it yourself

Write rect_scale, which takes a pointer to a rectangle and scales its width and height in place.

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
before: 10.00 m2
after:  22.50 m2 (6.00 x 3.75)