This is the drill floor for Pointer to pointer . The parent note taught you what int ** is and why dereference "peels a layer." Here we hit every case class that pointers-to-pointers can throw at you — normal chains, broken chains (NULL), function calls that must reach back, string arrays, dynamic memory, and the classic exam traps — and we work each one until nothing surprises you.
Intuition Keep this picture in your head the whole way
A pointer-to-pointer is a chain of notes . Each note says "the next thing is over there." Each * in C means "walk one step along the chain." & means "write down where this thing lives" (add a note in front). Every example below is just how many steps do we walk , and which link do we overwrite .
Figure 1 (below) — the master picture for the whole page. It shows the three linked boxes pp → p → x laid out left to right. The takeaway to burn in: pp on its own is zero walks (holds 0x200), one black arrow (*pp) reaches p (holds 0x100), and the second arrow — drawn in red to the treasure box — is what **pp reaches: the value 42. Two of the three names are addresses ; only the red-highlighted end is the actual number.
Every problem in this topic lands in one of these cells. The worked examples that follow each carry a [Cell …] tag so you can see the whole grid is covered.
Cell
Case class
What makes it tricky
Example
A
Normal chain read (pp → p → x)
count arrows correctly
Ex 1
B
Writing at each level (*p, then **pp, then *pp)
which link you overwrite
Ex 2
C
Broken chain (p == NULL)
**pp crashes; *pp is fine
Ex 3
D
Function changes caller's pointer
pass &q, use int **
Ex 4
E
Dynamic memory via int **
malloc result escapes the function
Ex 5
F
Array of strings (char **)
*pp = string, **pp = one char
Ex 6
G
Pointer arithmetic on **
pp+1 steps by sizeof(pointer)
Ex 7
H
Degenerate / self-reference twist
pointer to itself, re-aiming a link
Ex 8
I
Exam trap: count-the-stars output
predict exact printed number
Ex 9
Note on Cell B: the three notations are listed in the exact order Example 2 executes them — first *p (line 1), then **pp (line 2), then *pp (line 3) — so you can follow the table and the example line-for-line.
We will assume this concrete memory layout throughout (invented but consistent addresses, so every "Verify" is checkable):
Using the reference layout, evaluate pp, *pp, and **pp. Give each as an address or a value .
Forecast: Guess before reading — how many of these three are addresses, and how many are the number 42?
Step 1 — pp alone.
pp is just note-2 with zero walks taken. It literally holds 0x200.
Why this step? No * means no dereference — you read the box's own contents. pp stores &p = 0x200.
Step 2 — *pp (walk one step).
One * follows the address in pp. We arrive at note-1, p, and read its contents: 0x100.
Why this step? *pp ≡ p (parent's layer-peeling rule). p holds &x = 0x100 — still an address .
Step 3 — **pp (walk two steps).
In plain words: **pp means "star, applied to *pp." We showed in Step 2 that *pp is p. So **pp is really *p. And *p means "follow p's address to the box it points at" — that box is x, which holds 42. Written as a chain of equal things:
∗ ∗ pp ≡ ∗ ( ∗ pp ) ≡ ∗ p ≡ x = 42
Read that line left to right as one sentence: "two stars on pp" is the same as "one star on (one star on pp)", which is the same as "one star on p", which is the same as x, which equals 42.
Why this step? The second * follows 0x100 to the treasure box x. Now we finally read a real int.
Verify: In the picture, count arrows: pp = start, *pp = after 1 arrow, **pp = after 2 arrows. Two of three are addresses (0x200, 0x100); exactly one is the value 42. ✅
So: pp = 0x200, *pp = 0x100, **pp = 42.
Start from the reference layout. Run these three lines in order and give the final x, p:
* p = 20 ; // (line 1)
** pp = 30 ; // (line 2)
* pp = NULL ; // (line 3)
Forecast: Does line 3 change x? Guess yes/no first.
Step 1 — *p = 20. One * on the left reaches x and stores 20. Now x == 20.
Why this step? The count of * on the left-hand side decides which box you overwrite . One walk from p lands on x.
Step 2 — **pp = 30. Two * also reach x (a longer route: pp → p → x). Now x == 30, overwriting the 20.
Why this step? **pp and *p name the same box x. Different route, same destination — so this write clobbers the previous one.
Step 3 — *pp = NULL. One * from pp lands on p, not x. We overwrite the pointer: p == NULL.
Why this step? *pp ≡ p. Writing here re-aims note-1; the treasure x is untouched.
Verify: Final x == 30 (unchanged by line 3), final p == NULL. Line 3 changed the pointer, not the int — matching the parent's rule "the level you write depends on how many * on the left." ✅
⚠️ After line 3, evaluating **pp would crash — see Example 3.
Figure 2 (below) — the same three lines drawn on the chain. The p box is drawn in red because it is the link that Line 3 re-aims: the picture shows *p = 20 and **pp = 30 both arriving at the x box (same destination, two routes), while *pp = NULL stops one box short and overwrites the red p box itself. Takeaway: trace where each red/black arrow ends — that is the box you overwrote.
Given:
int * p = NULL ;
int ** pp = & p;
Which of pp, *pp, **pp are safe to read, and which is undefined behaviour ?
Forecast: How many arrows can you safely walk before hitting the NULL wall?
Step 1 — pp. pp holds &p, a real address (p exists on the stack). Safe.
Why this step? pp points at p, and p is a valid variable — the box is real even though its contents are NULL.
Step 2 — *pp. One walk lands on p; reading it yields NULL (i.e. 0). Safe — reading a NULL value is fine.
Why this step? We only read the contents of p. Reading the number 0 never crashes; dereferencing it does.
Step 3 — **pp. The second * tries to follow NULL to "the box at address 0." There is no such box. Undefined behaviour → crash.
Why this step? **pp ≡ *p ≡ *(NULL). Dereferencing a null pointer is the single most common pointer crash.
Verify: Safe walks = 1 (pp is step 0, *pp is step 1, both valid). The chain breaks at the second walk because link p is NULL. So safe reads = {pp, *pp}, crash = {**pp}. ✅
*pp didn't crash, **pp is fine too."
Why it feels right: they look like the same expression with one extra star.
The fix: *pp only reads the value stored in p. **pp uses that value as an address. A NULL value reads fine but is deadly to dereference. Each link must be non-null before you take the next step.
Predict the output. Related to Pass by value vs pass by reference .
void redirect ( int ** pp , int * target ) {
* pp = target; // overwrite caller's pointer
}
int main ( void ) {
int a = 5 , b = 9 ;
int * q = & a; // q aims at a
redirect ( & q, & b); // ask function to re-aim q
printf ( " %d\n " , * q); // ???
}
Forecast: Does *q print 5 or 9?
Step 1 — q = &a. q points at a, so *q == 5 right now.
Why this step? Establish the "before" state.
Step 2 — pass &q (type int **). Inside the function, pp holds the address of main's q.
Why this step? C passes by value . Passing q itself would copy the pointer value — the function could only change its local copy. Passing &q hands over the address of q, so the function can reach back and overwrite the real q.
Step 3 — *pp = target. One * on the left lands on main's q; we store &b into it.
Why this step? *pp ≡ q (the caller's variable). This re-aims q at b.
Step 4 — back in main, *q. q now holds &b, so *q == 9.
Verify: Output is 9. If the parameter had been int *pp (one star) instead, only a local copy would change and the answer would wrongly stay 5. The extra star is what reaches the caller. ✅
See Dynamic memory allocation (malloc) . Predict the printed value.
int make ( int ** out , int val ) {
* out = malloc ( sizeof ( int )); // put fresh address into caller's pointer
if ( * out == NULL ) return 0 ; // allocation failed
** out = val; // write into the new int
return 1 ; // success
}
int main ( void ) {
int * r = NULL ;
if ( make ( & r, 77 ))
printf ( " %d\n " , * r); // ???
free (r);
}
Forecast: Why can't make just take int * and do out = malloc(...)?
Step 1 — pass &r. out (type int **) receives the address of main's pointer r.
Why this step? We need make to change r itself (from NULL to a heap address). Changing a pointer from a function requires one more level — exactly Cell D's logic applied to malloc.
Step 2 — *out = malloc(sizeof(int)). One * lands on r; store the fresh heap address there.
Why this step? *out ≡ r. Now main's r points at valid heap memory. (If we had written out = malloc(...) with no star, we'd overwrite the local copy out — r would stay NULL.)
Step 3 — null-check *out. If malloc returned NULL, we bail with 0.
Why this step? Cover the degenerate case: allocation can fail. Never dereference an unchecked malloc.
Step 4 — **out = val. Two walks reach the new int; store 77.
Why this step? **out ≡ *r ≡ the heap int. This is the write into freshly-owned memory.
Verify: make returns 1, *r == 77 is printed, then freed. Passing int * (one star) instead of int ** would leave r == NULL and the printf would crash — proving the second star is mandatory. ✅
Related to Command line arguments (argv) . Give the output of each printf.
char * names [ 3 ] = { "Ann" , "Bo" , "Cy" };
char ** pp = names; // pp aims at names[0]
printf ( " %s\n " , * pp ); // (a)
printf ( " %s\n " , * (pp + 2 )); // (b)
printf ( " %c\n " , ** pp ); // (c)
printf ( " %c\n " , * ( * pp + 1 )); // (d)
Forecast: Which lines print a whole word , and which print a single letter ?
Step 1 — *pp (a). One * peels pp to the first char*, i.e. names[0] = "Ann". %s prints the whole string.
Why this step? pp points at an array of char*. One walk gives a char* — a string pointer.
Step 2 — *(pp + 2) (b). pp + 2 steps forward by 2 char* slots to names[2]; * gives "Cy".
Why this step? Pointer arithmetic on char ** moves by sizeof(char*) — see Pointer arithmetic . So pp+2 lands on the third string pointer.
Step 3 — **pp (c). Two walks: pp → names[0] → first char. That's 'A'.
Why this step? First * = string pointer; second * dereferences a char* to a single char.
Step 4 — *(*pp + 1) (d). *pp is "Ann"'s start; +1 moves one char along; * reads 'n'.
Why this step? Here the +1 is on a char*, so it steps by sizeof(char) = 1 byte — the second character.
Verify: (a) Ann, (b) Cy, (c) A, (d) n. Whole words come from a single * (giving char*); letters come from ** or *( .. + k) reaching an actual char. ✅
Figure 3 (below) — the string array drawn as two columns. Left column is the char* array (names[0..2]), right column is the actual text of each string; the red highlight marks names[0] and its string "Ann", which is the target of *pp and (one more step) **pp. Takeaway: one arrow lands in the right column on a whole word (a char*), and only a second step into the letters of that word gives you a single char — that is why *pp prints Ann but **pp prints A.
int a = 1 , b = 2 , c = 3 ;
int * row [ 3 ] = { & a, & b, & c}; // three int-pointers
int ** pp = row; // pp aims at row[0]
printf ( " %d\n " , ** (pp + 1 )); // ???
Forecast: Will this print 1, 2, or 3?
Step 1 — pp + 1. Steps forward by one int* slot → address of row[1].
Why this step? The pointed-to type of pp is int*, so +1 advances by sizeof(int*), landing on the next element of row.
Step 2 — *(pp + 1). One walk gives row[1] = &b.
Why this step? Reading the slot yields the pointer stored there, which is &b.
Step 3 — **(pp + 1). Second walk follows &b to b = 2.
Why this step? Two arrows total: array element → the int it points at.
Verify: Output 2. The arithmetic hops one slot (not one byte), because the base type is int*. ✅ (Contrast Ex 6 step 4, where +1 on a char* moved one byte.)
int x = 100 , y = 200 ;
int * p = & x;
int ** pp = & p;
* pp = & y; // (line 1)
** pp = 999 ; // (line 2)
// final x, y, *p ?
Forecast: After both lines, which variable equals 999 — x or y?
Step 1 — *pp = &y. *pp ≡ p, so this re-aims p from &x to &y.
Why this step? We overwrite the link , not the treasure. p now points at y; x is abandoned but unchanged.
Step 2 — **pp = 999. Now **pp ≡ *p ≡ y, so store 999 into y.
Why this step? Because we moved the link in step 1, the same expression **pp now targets a different box than it would have before. This is the subtle part: **pp follows the current value of p.
Verify: Final x == 100 (never touched), y == 999, *p == 999 (since p now points at y). Changing where a link points changes what future **pp writes hit. ✅
A classic single-answer exam question. What does this print?
int arr [ 3 ] = { 10 , 20 , 30 };
int * p = arr; // p aims at arr[0]
int ** pp = & p;
( * pp) ++ ; // (line 1) -- note the parentheses!
printf ( " %d\n " , ** pp );
Forecast: Does (*pp)++ change a value (making it 11) or move a pointer (to the next element)?
Step 1 — decode (*pp). *pp ≡ p, which is an int* pointing at arr[0].
Why this step? The parentheses force *pp to be evaluated first, so ++ applies to the pointer p, not to the int.
Step 2 — (*pp)++. Increment the pointer p by one int → now p points at arr[1].
Why this step? p++ is pointer arithmetic: advance by sizeof(int) to the next array element. It does not touch any stored integer, so arr[0] is still 10.
Step 3 — **pp. Two walks: pp → p (now &arr[1]) → arr[1] = 20.
Why this step? **pp ≡ *p, and p now aims at arr[1], whose value is 20. So the program prints 20.
Verify: Output 20. The trap: many read (*pp)++ as "increment the value" (expecting 11). The parentheses make it increment the pointer , so we skip to the next element and read arr[1] == 20, not arr[0] + 1 == 11. Compare (**pp)++, which would give 11 by incrementing the int in arr[0]. ✅
(*pp)++ vs (**pp)++
Why it feels right: both have a ++ and some stars.
The fix: Count stars inside the parentheses. (*pp) is a pointer → ++ moves it one element. (**pp) is an int → ++ adds 1 to the number. One star = move a link; two stars = change the value.
Recall Cover-and-recall: which cell is which?
Ann/Bo/Cy string array — which cell? ::: F (array of strings, char **)
*pp = NULL overwriting the pointer — which cell? ::: B (writing at a level)
redirect(&q, &b) changing main's pointer — which cell? ::: D (function changes caller's pointer)
**pp crashing because p is NULL — which cell? ::: C (broken chain)
(*pp)++ moving a pointer not a value — which cell? ::: I (count-the-stars exam trap)
"Left stars pick the box; right stars fetch the value; +1 hops by the pointed-to size."
Every example above is one of those three rules applied carefully.
Pointer to pointer (index 5.1.12) — the parent concept this drills.
Pointers basics — single-level reads/writes underlie every step.
Pass by value vs pass by reference — Example 4's whole reason to exist.
Dynamic memory allocation (malloc) — Example 5's int ** return pattern.
Command line arguments (argv) — Example 6 is char **argv in miniature.
Pointer arithmetic — Examples 6, 7, 9 hinge on +1 stepping by the pointed-to size.
2-D arrays vs pointer to pointer — Example 7's int *row[] vs a true 2-D array.