The C Path — learn C, visually

🎯 Pointers & Memory

Pointers: variables that hold addresses

⏱ 14 min · free interactive lesson · quizzes, visualizations & a real compiler

▶ Open the interactive lesson — free, no signup
Why you're learning this

Back in Part 1 you typed scanf("%d", &x) and were told "just put the & there." You were secretly handing over your variable's address — today the secret comes out. It cracks a genuine puzzle, too: a function that tries to swap two variables and silently changes nothing. By the end you'll know exactly why it fails, and you'll have the tool that fixes it.

This is it — the lesson C is famous for. A pointer is nothing mystical: it is a variable whose value is the address of another variable. You already know every variable lives somewhere in memory; a pointer simply writes that "somewhere" down so you can come back to it later.

Every variable has an address

The & operator (read: "address of") asks: where does this variable live? You can print the answer with printf's %p (p for pointer); the (void *) in front converts the address to the generic "pointer to anything" type that %p expects:

address.c
#include <stdio.h>

int main(void) {
    int x = 42;

    printf("value  : %d\n", x);
    printf("address: %p\n", (void *)&x);
    printf("size   : %zu bytes\n", sizeof x);
    return 0;
}
terminal
$ gcc address.c -o address && ./address
value  : 42
address: 0x7ffee4c01a9c
size   : 4 bytes
# your address WILL differ — that's normal (ASLR)

Your address will differ — and change between runs, because modern OSes randomize the layout (ASLR). The exact number never matters; what matters is that there is one, and you can store it.

🧠 Checkpoint: Given int x = 5;, what is &x?

  • The value 5
  • A copy of x
  • The memory address where x is stored
  • Always 0x100
Show answer

The memory address where x is stored& is the address-of operator: it yields where x lives (a value of type int *), not what x contains.

Pointer variables: int *p

int *p = &x; declares p as a pointer to int and stores x's address in it. The type matters: p doesn't just remember an address, it remembers that an int lives there — which controls how many bytes a read grabs, and (next lesson) how far p + 1 jumps.

⚠️

The * binds to the name, not the type. int* a, b; declares a pointer a and a plain int b! That's why C veterans write the star next to the variable: int *a, *b;. One star per pointer, always.

This spot has an interactive memgrid widget — open the interactive lesson to play with it.

The * operator (read: "the thing at") dereferences a pointer — it follows the address. *p is, for all purposes, another name for x: reading *p reads x, and writing *p = 99; changes x. Yes, the same symbol declares pointers and dereferences them. Context tells them apart; it's confusing for about a week, then permanent.

🧠 Checkpoint: After int *p = &x;, what does *p = 10; do?

  • Makes p point to address 10
  • Sets x to 10
  • Undefined behavior
  • Declares a second pointer
Show answer

Sets x to 10 — Dereferencing follows the stored address, so the assignment writes 10 into x’s memory. p itself (the address it holds) is unchanged.

NULL: pointing at nothing

Sometimes a pointer has nowhere to point yet. For that, C provides NULL — a special value guaranteed to compare unequal to the address of any real object. It's the universal "empty" sentinel: functions return it on failure, lists end with it, and you should initialize pointers to it instead of leaving them as random garbage.

💀

Dereferencing NULL is undefined behavior — on mainstream systems it's the classic segmentation fault. Before following a pointer that might be empty, check it: if (p != NULL) ... (or just if (p) — NULL is falsy).

The payoff: swap()

Why do we even need pointers? Here's the classic motivation. Try to write a function that swaps two ints — without pointers:

swap_broken.c
#include <stdio.h>

void swap_broken(int a, int b) {   /* receives COPIES  */
    int tmp = a;
    a = b;
    b = tmp;                       /* swaps the copies */
}                                  /* copies die here  */

int main(void) {
    int x = 3, y = 7;
    swap_broken(x, y);
    printf("x=%d y=%d\n", x, y);
    return 0;
}
terminal
$ gcc swap_broken.c -o swap && ./swap
x=3 y=7
# completely unchanged — we only swapped copies

Nothing happened! C passes arguments by value: swap_broken received copies of x and y, dutifully swapped the copies, and threw them away on return. To modify the caller's variables, we must pass their addresses — then the function can reach back through them. Step through the fixed version:

This spot has an interactive trace widget — open the interactive lesson to play with it.

🧠 Checkpoint: Why does swap_broken(x, y) fail to swap?

  • Ints can’t be swapped in C
  • The compiler optimizes the call away
  • C passes arguments by value — the function gets copies
  • swap must return two values, which C forbids
Show answer

C passes arguments by value — the function gets copies — Every C argument is copied into the callee’s parameters. To let a function modify YOUR variable, hand it the variable’s address — that’s what pointer parameters are for.

This pattern — pass an address so the callee can modify your variable — is everywhere in C: scanf("%d", &x) is exactly it.

This spot has an interactive editor widget — open the interactive lesson to play with it.

You can now hold an address. Next: the surprisingly clever rules for doing arithmetic on one.

▶ Practice this lesson interactively (with live gcc)