5.1.12 · D1C Programming

Foundations — Pointer to pointer

3,133 words14 min readBack to topic

Before you can safely read a line like this:

int   x  = 42;   // an int
int  *p  = &x;   // p is a pointer to int, holds x's address
int **pp = &p;   // pp is a pointer to pointer, holds p's address

…you must be able to read every piece of every line without guessing. This page takes each symbol and concept the parent uses and builds it from nothing — plain words, a picture, and the reason the topic needs it. Read top to bottom; each item leans on the one above it. We introduce the byte and the box first, then a value, then a pointer, then a pointer to pointer, so nothing is ever used before it is built.


1. Memory as a street of numbered boxes

Everything in this topic sits on one mental picture: computer memory is a long row of numbered boxes. Each box has a permanent number called its address.

Figure — Pointer to pointer

What the figure shows: five memory boxes (bytes) in a row. Four hold ... (some other data); the highlighted middle box holds the value 42. Under each box is its address (0x0FE, 0x0FF, 0x100, …). The yellow arrow marks that the value 42 sits at address 0x100. Notice: the number under the box (address) and the number inside the box (value) are two completely different things.

Why the topic needs it: a pointer's whole job is to store an address. If you don't picture memory as numbered boxes, the words "pointer holds an address" mean nothing. See Pointers basics for the single-level version.


2. The type — how many boxes, and how to read them

Before we name any box, we need the idea that tells the computer what kind of thing a box holds.

Why the topic needs it: every pointer type in the parent (int *, int **, char **argv) is built by attaching stars to a base type. You cannot read int ** until you know what the int part promises.


3. A variable and its name

  • x — the human name for the box.
  • 0x100 — the box's address (chosen by the computer, not you).
  • 42 — the value inside.

The name x and the address 0x100 refer to the same box — one is for humans, one is for the machine.

Why the topic needs it: pointers store the addresses of variables. p = &x only makes sense once x is a real named box with a real address to point at.


4. Indirection — the whole idea in one word

Before any more symbols, name the idea that drives the entire topic.

Why the topic needs it: "pointer to pointer" literally means "two levels of indirection." Every & you add is one more level up; every * you follow is one level down.


5. & — the address-of operator

Picture it as pointing at the box and reading its number off the label.

Why the topic needs it: to make a pointer point somewhere, you must hand it an address — and & is the only way to obtain one from a variable.


6. * in a declaration — "this is a pointer"

Here is the trap that confuses everyone: * means two different things depending on where it appears.

Figure — Pointer to pointer

What the figure shows: two boxes. The pink box on the left is p (address 0x200); its value is 0x100. The yellow box on the right is x (address 0x100); its value is 42. A chalk arrow runs from p's box to x's box, because p's stored value is x's address. "Following the arrow" and "reading the address stored in p" are the same act.

Why the topic needs it: a "pointer to pointer" is built by adding one more *. If you can't read one *, two will be gibberish.


7. * in an expression — dereference, "follow the arrow"

Why the topic needs it: *pp and **pp in the parent are dereferences. Each * is one "follow."


8. & and * are opposites — the wrap/peel pair

Figure — Pointer to pointer

What the figure shows: three stacked labels — the value x = 42 at the bottom, its address &x = 0x100 in the middle, and "address of that, &p" at the top. The pink arrow points upward (& wraps to a higher address level); the blue arrow points downward (* peels back to the value). They are mirror moves on the same ladder.

This single ladder picture is the whole engine of the topic. Adding * to a type stacks more arrows to follow; adding & when you assign stacks more addresses to store.


9. Stacking one more level — the pointer to pointer

Now every piece is earned, we can read the parent's key line — and x and p already exist from the code above, so pp has something real to point at.

int   x  = 42;   // int      at 0x100, value 42
int  *p  = &x;   // int*     at 0x200, value 0x100
int **pp = &p;   // int**    at 0x300, value 0x200
Figure — Pointer to pointer

What the figure shows: three boxes left to right — pp (blue, at 0x300, value 0x200), p (pink, at 0x200, value 0x100), x (yellow, at 0x100, value 42). Two arrows chain them: pp → p (labelled *pp) and p → x (the second *). Following both arrows lands on 42, which is what **pp does.

Reading the declaration with the tools above:

  • int **pp — declaration * (×2): "pp is a pointer to a pointer to int."
  • pp = &p& wraps: pp stores p's address (0x200).
  • *pp — one dereference (peel one): follow to p, which is an int *still a pointer, not the value yet.
  • **pp — two dereferences (peel twice): follow to p, then follow p to x — the actual value 42.
Recall Why exactly two

* to reach the value? Because there are two arrows between pp and x. One * crosses one arrow (lands on p); the second * crosses the next (lands on x). The number of * equals the number of arrows you must cross — two-arrow chain, two dereferences.


10. NULL — the note that points nowhere

Why the topic needs it: the parent's crashes (**pp after *pp = NULL) come from trying to follow an arrow that leads nowhere. Following a NULL arrow means walking to address 0, which holds no valid box.


11. Passing by value — why functions can't change your pointer

This is the deep reason int ** exists (parent's WHY case 1). To let a function change your pointer q, you can't hand it q (a copy) — you hand it &q (the address of q), which has type int **. Full treatment in Pass by value vs pass by reference.


Prerequisite map

The diagram below shows how each foundation feeds the next. Read this paragraph as the full description in case the diagram does not render: Memory as numbered boxes is built from the byte (the smallest box) and gives every box an address. A type (like int) says how many bytes a value uses and how to read them. A variable is a named box, built from both the address idea and the type idea. The & (wrap up) operator reads a variable's address; the * (peel down) operator follows an address back to a value. A pointer is built from type + & + * (it stores an address). Stacking one more &/* level on a pointer produces a pointer to pointer — the two-arrow chain. A pointer can also be set to NULL (points nowhere), and the copying rule of a variable gives pass by value. The pointer-to-pointer, NULL, and pass-by-value ideas are the three that feed directly into the parent topic 5.1.12.

Byte smallest box

Memory as numbered boxes

Address box number

Type size plus reading rules

Variable named box

Ampersand address-of wraps up

Star dereference peels down

Pointer holds an address

Pointer to pointer two arrows

NULL points nowhere

Pass by value copies

Pointer to pointer topic 5.1.12


Equipment checklist

You are ready for the parent note when you can answer each of these without peeking. Each line is written as Question ::: Answer — cover everything right of the ::: and check yourself.

What is a byte?
The smallest chunk of memory with its own number — one box on the street; the smallest addressable unit.
What does the 0x prefix mean on an address like 0x100?
It flags the number as hexadecimal (base-16); the prefix is a label, not part of the value.
What is an address in one sentence?
The number of the memory box where a value lives.
What is the difference between the value 42 and the address 0x100?
42 is what's inside the box; 0x100 is the box's number (where it is).
What does the type int tell the compiler?
How many bytes the value takes and how to read the bits (a whole number).
What is a variable?
A named box holding a value of some type; its name and its address refer to the same box.
What is indirection?
Holding directions to a thing instead of the thing itself; a pointer to pointer is two levels of it.
What does &x give you?
The address of x — it wraps one level up.
In int *p = &x;, what does the * mean?
It's part of the type: p is a pointer to int, and = &x fills it with x's address.
In *p = 5;, what does the * mean?
Dereference — follow p's arrow and write into the box it points to.
What does the symbol ≡ mean in *p ≡ x?
Definitional equivalence — both sides are by definition the same box, not just numerically equal by chance.
Why are & and * opposites, and when do you need parentheses?
& goes value to address (up), * goes address to value (down); group as *(&x) or &(*p) to avoid precedence surprises.
What does int **pp hold?
The address of a pointer — two arrows to follow before reaching the value.
Why does it take two * to read the final value through pp?
There are two arrows in the chain; each * crosses one.
What does NULL mean and why is dereferencing it dangerous?
A pointer to nowhere (address 0); dereferencing it is undefined behaviour — may crash, corrupt, or falsely seem to work.
Why must you pass &q (not q) to let a function change your pointer?
C passes a copy; only the address of q lets the function reach the real q.

Connections

  • Parent: Pointer to pointer — the topic these foundations feed into.
  • Pointers basics — the single-arrow version of everything here.
  • Pass by value vs pass by reference — why copies force us up a level.
  • Dynamic memory allocation (malloc) — where int ** returns fresh memory.
  • Command line arguments (argv)char **argv uses this exact stacking.
  • Pointer arithmetic — stepping a pointer by box-size.
  • 2-D arrays vs pointer to pointer — same picture, subtly different memory layout.