5.1.8 · D2C Programming

Visual walkthrough — Pointers — declaration, dereferencing ( - ), address-of (&)

1,876 words9 min readBack to topic

We will follow this tiny program the whole way down. Keep it in the corner of your eye:

int x = 42;
int *p = &x;
*p = 99;

Step 1 — A patch of memory with nothing in it yet

WHAT. Before any variable exists, memory is just a row of numbered slots. Each slot holds one byte (a byte is the smallest addressable chunk — think "one small box"). The number written under a box is its address — its house number on the street.

WHY start here. Every later idea — value, pointer, dereference — is just "which box, and what's written in it." If you can see the empty boxes and their numbers, everything else is bookkeeping.

PICTURE. Below: a strip of boxes. The white number under each box is its address. The boxes are empty (--) because we haven't declared anything.


Step 2 — int x = 42; puts a value into named boxes

WHAT. The line int x = 42; does two things:

  1. Reserves space for one int. An int is (typically) 4 bytes, so it claims 4 boxes — say addresses 0x10000x1003.
  2. Writes the number 42 across those 4 boxes.

From now on the name x is a nickname for that group of boxes starting at 0x1000.

WHY 4 boxes. The type int decides the size. The compiler must know how many boxes to grab and how to read them back as one number. Different types = different box counts, but the idea is identical.

PICTURE. The four boxes light up amber, grouped under the label x, with 42 written inside. The starting address 0x1000 is what we'll care about — that's the "front door."


Step 3 — &x asks: "what is the front-door address?"

WHAT. The operator & (address-of) does not look inside the boxes. It hands you the number of the front door. So &x evaluates to 0x1000.

WHY this operator exists. We often need to talk about where something lives, not what's in it — for example to tell a function "here's the box, go change it yourself." & is the only way to turn a variable into its address.

PICTURE. A cyan arrow labelled &x points at the front of the x group and reports the number 0x1000. Notice the arrow touches the edge (the address), not the contents.


Step 4 — int *p = &x; makes a house that stores a house number

WHAT. int *p reserves its own boxes for a pointer (a pointer is also just some boxes — say at 0x2000). Then = &x writes the number 0x1000 into those boxes. So p is a variable whose stored value is an address.

WHY the type is int *. The star in the declaration tags p with "the box I point at holds an int." That tag is not decoration — it tells C how many boxes to read later, and (in Pointer Arithmetic) how far one step moves.

PICTURE. A new group of boxes appears (that's p, at 0x2000). Inside them is written 0x1000. A cyan arrow now springs from p's boxes and lands on x's front door — that arrow is the stored number, drawn as a picture.


Step 5 — *p follows the arrow and reads what's there

WHAT. The operator * (dereference) means: "take the address stored in p (0x1000), go to those boxes, and read an int." The result is 42 — the same value as x. So right now *p == x.

WHY this operator exists. A stored address is useless until you can use it. * is the "go there and look" step — the return trip that & set up.

PICTURE. Trace the arrow from p to x, then reach inside x's boxes and pull out 42. The star operator lands on the contents, not the edge — the opposite of what & did.


Step 6 — *p = 99; writes through the arrow, changing x

WHAT. On the left of =, *p does not read — it names a destination. "Go to 0x1000 and write 99 there." Because those boxes are x, the value of x becomes 99. We never mentioned x by name, yet x changed.

WHY this is the whole point of pointers. This single move is how a function reaches out and edits a variable it doesn't own (see Pass by Reference vs Pass by Value). The pointer is a remote control for someone else's boxes.

PICTURE. The value inside x's boxes flips from 42 to 99. The arrow from p is unchanged — same address, new contents at the far end.


Step 7 — The degenerate case: p = NULL, an arrow pointing nowhere

WHAT. A pointer must always hold some number. int *p = NULL; stores the special address 0 — a deliberate "I point at nothing" flag. Reading *p now means "go to box 0 and look," which the operating system forbids. The program crashes (a segmentation fault).

WHY show this. Every earlier step assumed the arrow lands on real boxes. The moment it doesn't, the shape of the operation is the same but the result is disaster. You must always check.

PICTURE. The arrow from p dangles off into the barred, forbidden zone at address 0 — no boxes to read. The dereference is drawn with a red cross.

int *p = NULL;        // arrow points at "nowhere"
if (p != NULL)        // ALWAYS check first
    printf("%d", *p); // only travel if the arrow lands somewhere real
Recall Predict then reveal

With int x = 42; int *p = &x; What is &x? ::: The address where x's boxes start, e.g. 0x1000. What is p storing? ::: That same address, 0x1000 (drawn as an arrow to x). What is *p before any write? ::: 42 — the value at 0x1000. After *p = 99;, what is x? ::: 99, because *p wrote through the arrow into x's boxes. What is *(&x)? ::: Just x — the round trip returns the value.


The one-picture summary

Everything on this page is one loop: x lives in boxes → &x grabs the front-door number → p stores that number (an arrow) → *p walks the arrow to read or write the boxes → writing changes x itself. & and * are the two directions of the same journey.

Recall Feynman: tell the whole walkthrough to a 12-year-old

Imagine a hallway of lockers, each with a number on the door. We fill locker 1000 with the note "42" and nickname it x. Now we grab a fresh locker, 2000, and inside it we don't put a note — we put the number of another locker: we write "go to 1000." That locker 2000 is our pointer p, and the little "go to 1000" note is really an arrow pointing at x. To read through the pointer (*p), you read the note ("go to 1000"), walk to locker 1000, and read its note — you get 42. To change x without ever touching its nickname, you follow the same arrow and rub out "42", writing "99" instead (*p = 99); now anyone who opens locker 1000 sees 99. The & step is just "what number is this locker's door?" — it hands you 1000. And *(&x) is a there-and-back: ask for x's locker number, then walk to that number and read — you land right back on x. The only rule: never follow an arrow that points at locker 0, the forbidden door — that's the crash.


Connections

  • Parent: Pointers overview
  • Pass by Reference vs Pass by Value — Step 6 is exactly how a function edits the caller's boxes.
  • Pointer Arithmetic — the int * type tag from Step 4 sets the step size.
  • Arrays in C — an array name is itself a "front-door" address like &x.
  • Dynamic Memory malloc free — arrows to boxes you request at runtime.
  • Strings as char pointers — the same picture with char boxes.
  • NULL and Segmentation Faults — the forbidden arrow of Step 7.