Field Notes № 03 · Memory An Interactive Visual

The Pointer,
visually.

A pointer is not magic. It is a small variable — eight bytes, on most machines — whose only job is to hold an address. Watch one come to life, move, dereference, and (occasionally) misbehave. No abstractions, no metaphors. Just memory.

01 ——

What it actually is.

A box that holds a number. The number is an address.

Every variable in your program lives at some address in memory. int x = 42; might place x at 0x1000. A pointer to x — written int *p = &x; — is itself a variable, living at its own address (say 0xFF20), and the value stored inside it is 0x1000. That's the entire idea. Two boxes, one holding 42, the other holding the address of the first.

A pointer is just a box that holds an address.

Hover the highlighted parts to see what each piece is.

lives at 0xFF20 0x1000 p the pointer 8 bytes points to lives at 0x1000 42 x int 4 bytes // in your code int x = 42; int *p = &x; // p holds 0x1000 two variables · two addresses · one points at the other
The address-of operator

&x

Reads "address of x." Returns the location where x lives. Type is int* if x is an int. This is how you put a real address into a pointer — there's no other way.

The dereference operator

*p

Reads "the thing p points to." Goes to the address stored in p, loads whatever is there. *p on the left side writes; *p on the right side reads.

02 ——

Dereferencing, animated.

Watch the pointer fetch a value, then write a new one.

The two operations every pointer ever performs: read through (load the value at the address it holds) and write through (store a new value at that address). Both follow the arrow.

Read and write, through the arrow.

Click the buttons to perform each operation.

p 0x1000 at 0xFF20 x 42 at 0x1000 v int v; 42 // pick an operation above
Ready. The pointer p currently holds 0x1000 — the address of x.
03 ——

Pointer arithmetic.

When p++ moves four bytes, not one.

Adding 1 to a pointer doesn't add 1 byte — it adds one element-size. For an int* on a typical machine, that's 4 bytes. For a double*, 8 bytes. The compiler knows the element type and silently scales the offset. This is why arr[i] and *(arr + i) land on the same cell: arr + i means "advance i × sizeof(element) bytes."

Move the pointer along an int array.

Each p++ jumps four bytes — exactly one int.

0x1000 0x1004 0x1008 0x100C 0x1010 10 20 30 40 50 arr[0] arr[1] arr[2] arr[3] arr[4] 0x1000 p p = 0x1000 → arr[0] = 10 int *p = arr; // sizeof(int) = 4 bytes per step
Step 0. Pointer initialized to arr, equivalent to &arr[0]. Currently points to 10.
04 ——

Pointer to pointer.

Indirection, taken one level further.

If a pointer holds the address of an int, then a pointer to a pointer holds the address of an int-pointer. Written int **pp. Three boxes, two arrows. Used everywhere: when a function needs to change a pointer the caller passed in (think argv, or how realloc lets you swap a buffer), or to build a 2D array of strings.

Two arrows. One value.

Click each step to see what **pp resolves to.

pp 0xFF20 int ** p 0x1000 at 0xFF20 x 42 at 0x1000 click step to begin
Ready. Three variables: x = 42, pointer p holds the address of x, pointer-to-pointer pp holds the address of p.
05 ——

When pointers lie.

NULL, dangling, wild — three kinds of trouble.

A pointer is just bytes. Nothing in those bytes guarantees they form a valid address. Three failure modes show up again and again, and recognizing them on sight is half the battle.

Three ways pointers go wrong.

Click each kind to see what's actually in memory.

p 0x1000 42 valid · int x all good · the pointer is valid and the target lives
Healthy. The pointer holds a valid address. Dereferencing returns 42.
06 ——

Five lines, memorized.

If these read fluently, you understand pointers.

Once these stop looking strange, everything in C/C++ that involves pointers is just composition of these five primitives.

Declare

int *p;

Reserves an 8-byte slot to hold an address. Currently holds garbage — declaring a pointer does not initialize it. Always assign before dereferencing.

Take address

p = &x;

Stores the address of x into p. Now p and &x hold the same value. The arrow is drawn.

Read through

int v = *p;

Follows the arrow. Loads whatever int lives at the address in p and copies it into v. x itself is untouched.

Write through

*p = 99;

Follows the arrow. Stores 99 at the address in p — which means x is now 99. The original variable changed, through the pointer.

Step forward

p++;

Adds sizeof(*p) bytes to p. For int*, four bytes. For double*, eight. The compiler scales the step by the element size, which is why arrays and pointers feel interchangeable when you write arr[i] — that's just *(arr + i) with the scaling already done.

Field Notes № 03 Set in Fraunces & JetBrains Mono Pointers, visually · 2026