CStructures & Pointers

Structures & Pointers

Structs are frequently accessed through pointers rather than by value — for the same reasons pointers matter everywhere else in C: avoiding copies, and letting a function modify the caller's data. A pointer to a struct works like a pointer to anything else, but C gives it its own dedicated member-access syntax: the arrow operator.

Declaring a pointer to a struct

C
struct Point {
    int x;
    int y;
};

struct Point pt = {3, 4};
struct Point *p = &pt;   /* p holds the address of pt */
Two ways to reach a member through a pointer

Since p is a pointer, not a struct itself, the dot operator cannot be applied to p directly — you first have to dereference it to get back to the actual struct, then use dot. That reads awkwardly because . binds tighter than *, so the dereference needs parentheses.

C
#include <stdio.h>

struct Point {
    int x;
    int y;
};

int main(void) {
    struct Point pt = {3, 4};
    struct Point *p = &pt;

    /* Form 1: explicit dereference, then dot. Parentheses are required. */
    printf("(*p).x = %d\n", (*p).x);

    /* Form 2: the arrow operator does the same thing, more cleanly. */
    printf("p->x   = %d\n", p->x);

    return 0;
}
(*p).x = 3
p->x   = 3

p->x is exactly equivalent to (*p).x — the arrow operator exists purely as convenient, less error-prone sugar for "dereference, then access a member." In practice, virtually all real C code uses -> and reserves (*p).x for explaining what -> actually does.

Why pass structs by pointer

Passing a struct to a function by value copies every single byte of that struct onto the function's stack frame. For a small struct like Point (two ints), that is cheap. For a struct holding several arrays, buffers, or many fields, the copy can be a real, measurable performance cost — and it happens every time the struct is passed, including when it's passed back out as a return value. Passing a pointer instead copies only the address (typically 4 or 8 bytes), regardless of how large the pointed-to struct is.

C
struct BigRecord {
    char buffer[4096];
    double samples[500];
    int    metadata[128];
};

/* Copies ~8KB+ every call — expensive and wasteful. */
void processByValue(struct BigRecord record) { /* ... */ }

/* Copies only a pointer, regardless of BigRecord's size. */
void processByPointer(struct BigRecord *record) { /* ... */ }
Large structs by value are a real cost
Passing large structs by value is one of the most common accidental performance mistakes in C code. If a struct is more than a few machine words in size, prefer passing a pointer to it — `const struct T *` if the function should not modify it, `struct T *` if it should.
Modifying the caller's struct through a pointer

This directly follows from how pass-by-reference works in C: since the function receives the actual address of the caller's struct, writes made through that pointer are visible to the caller after the function returns. This is the standard way to write functions that update a struct "in place."

C
#include <stdio.h>

struct Point {
    int x;
    int y;
};

void translate(struct Point *p, int dx, int dy) {
    p->x += dx;
    p->y += dy;
}

int main(void) {
    struct Point pt = {0, 0};
    translate(&pt, 5, 3);
    printf("pt = (%d, %d)\n", pt.x, pt.y);
    return 0;
}
pt = (5, 3)

Without the pointer, translate would only be able to modify its own local copy of the struct, and pt in main would remain unchanged after the call returns.

Syntax

Meaning

Typical usage

s.member

Access a member of a struct value directly

When you have an actual struct variable, not a pointer

(*p).member

Dereference the pointer, then access the member

Rare in practice — mostly used to explain ->

p->member

Shorthand for (*p).member

The idiomatic way to access members through a pointer

Const-correctness for read-only access
If a function only needs to read a struct through a pointer and should never modify it, declare the parameter as `const struct Point *p`. This documents the intent, and the compiler will flag any accidental attempt to write through `p`.
  • p->member is shorthand for (*p).member — both mean the same thing.

  • Passing a pointer avoids copying the whole struct, which matters as structs grow.

  • A function that receives a pointer can modify the caller's original struct.

  • Use const struct T * for pointer parameters that should only read, never write.

Next: structs and functions
The next page looks specifically at passing and returning structs from functions — by value versus by pointer — and compares the trade-offs side by side with a worked example of each.