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