5.1.12 · D5C Programming

Question bank — Pointer to pointer

1,504 words7 min readBack to topic

Setup used throughout (memorise it):

int   x  = 42;
int  *p  = &x;    // p holds the address of x
int **pp = &p;    // pp holds the address of p

So pppx. Each * walks one arrow forward; each & walks one arrow backward.


True or false — justify

True or false: **pp and x name the exact same integer object.
True — *pp is p, and *p is x, so **pp is the storage of x; writing **pp = 7 changes x to 7.
True or false: *pp and p are the same thing.
True — pp holds &p, so following that one arrow lands you on p itself; *pp is just another name for the pointer p.
True or false: pp and &p hold the same value.
True — that is exactly what int **pp = &p; stored, so pp == &p is always true here.
True or false: int **pp reserves memory for the int it eventually reaches.
False — pp only stores one address (a pointer's worth of bytes); the actual int lives wherever x lives, allocated separately.
True or false: If you have int **pp, then sizeof(pp) equals sizeof(int).
False — pp is a pointer, so sizeof(pp) is a pointer size (often 8 bytes), independent of sizeof(int).
True or false: Passing int ** to a function lets it change the caller's pointer.
True — C copies arguments by value, so only by handing over the pointer's address (int**) can the function reach back and overwrite it — see Pass by value vs pass by reference.
True or false: char **argv means the program receives a single string.
False — it means an array of string pointers; each argv[i] is a char* to one command-line word — see Command line arguments (argv).
True or false: After *pp = NULL, the value in x becomes 0.
False — *pp is p, so you overwrote the pointer p with NULL; x still holds 42, but p no longer points to it.
True or false: **pp will always safely deliver an int.
False — it is only safe when *pp (i.e. p) points to valid memory; if p is NULL or dangling, **pp dereferences garbage.
True or false: pp + 1 and p + 1 step forward by the same number of bytes.
False — pp is int**, so pp+1 moves by sizeof(int*); p is int*, so p+1 moves by sizeof(int) — see Pointer arithmetic.

Spot the error

int x=5; int **pp=&x; — what's wrong?
&x is an int*, but pp expects an int** (address of a pointer). You skipped a level — you need an intermediate int *p=&x; then pp=&p;.
void f(int *ptr){ *ptr = malloc(sizeof(int)); }
— why won't this update the caller's pointer?
The parameter is int* (one level), so *ptr writes to the pointed-at int, not to the caller's pointer variable. To modify the pointer you need int **ptr and *ptr = malloc(...) — see Dynamic memory allocation (malloc).
int *p=&x; int **pp=&p; printf("%d", *pp); — what's the bug?
*pp has type int* (an address), not int, so %d reads it as an integer — a type mismatch. You want **pp for the int, or print *pp with %p.
int **pp;        // uninitialised
**pp = 3;
— why does this crash?
pp holds garbage, so *pp follows a random address to a random "pointer," and **pp follows that garbage into unmapped memory — undefined behaviour before you ever reach a real int.
char **p = "hi"; — why is this wrong?
A string literal "hi" is a char* (address of the first char), not a char**. The levels don't match; p would need something whose address is a char*.
int *q = NULL;
alloc_int(q);    // instead of &q
— what breaks?
Passing q copies the NULL value; the function edits only its private copy, so q in main stays NULL. You must pass &q (an int**) so the function can overwrite q itself.

Why questions

Why does the type need two asterisks in int **pp?
The type tells the compiler how to read the bytes at the target; int** says "the thing here is itself a pointer," so *pp yields a pointer, not an int.
Why does each * "peel exactly one layer"?
Dereference means "go to the address stored here and read the object there"; each * follows one arrow, so two * follows two arrows down to the final value.
Why does the number of * needed scale with what you want to modify?
To change an int you pass int* and write with one *; to change an int* you must go one level higher — pass int** and write with one * to reach the pointer.
Why is *pp = NULL different from **pp = 0?
*pp is the pointer p, so the first breaks the link (p no longer points to x); **pp is the value x, so the second only changes the data, leaving the link intact.
Why can't a plain int* do the job of char **argv?
argv must index multiple strings, each itself an address; one level (char*) reaches individual chars, but you need a level that holds addresses-of-strings — that's char**.
Why does **pp still equal 42 even after int *r = pp; fails to compile?
The two are unrelated: **pp follows the valid pp→p→x chain to 42; assigning pp (an int**) to int *r is a level mismatch and simply won't compile, changing nothing.

Edge cases

If p == NULL but pp == &p, is *pp safe to read?
Yes — *pp just reads the pointer p (which happens to be NULL); reading its value is fine. It is **pp that would crash, because it dereferences NULL.
If pp itself is NULL, is *pp safe?
No — *pp follows NULL immediately, which is undefined behaviour; even the first peel already crashes.
Can two different int** variables point to the same int*?
Yes — int **a=&p; int **b=&p; both hold &p, so *a and *b are the same pointer p; writing *a=NULL is visible through b.
What does **pp mean if x is itself a valid array-of-strings element via char**?
For char **pp over strings, *pp is a char* (a whole string) and **pp is a single char (the first character of that string) — layout differs from true 2-D arrays, see 2-D arrays vs pointer to pointer.
Is int **pp = 0; (assigning literal 0) legal and meaningful?
Yes — 0 in pointer context is the null pointer, so pp becomes NULL; it's legal but any *pp/**pp on it is a crash until you point it somewhere real.
What is the deepest you can legally chase before a crash if only pp is valid and p is dangling?
You may safely evaluate pp and *pp (reading the dangling value of p), but the moment you write **pp you follow that dangling address into invalid memory — undefined behaviour.

Recall One-breath summary

pp→p→x. * walks forward, & walks backward. *pp is p (a pointer), **pp is x (a value). Safety needs every arrow in the chain to point somewhere real; a broken link means the last peel crashes. To modify a thing, pass a pointer to that thing and use one * — which is why modifying a pointer needs **.


Connections

  • Pointer to pointer — the parent note these traps drill.
  • Pointers basics — single-level foundation every trap assumes.
  • Pass by value vs pass by reference — the "why ** for functions" traps.
  • Command line arguments (argv) — the char **argv traps.
  • Dynamic memory allocation (malloc) — the "return allocated memory" traps.
  • Pointer arithmetic — the pp+1 vs p+1 stepping trap.
  • 2-D arrays vs pointer to pointer — the layout edge case.