5.1.12 · D5C Programming
Question bank — Pointer to pointer
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 pSo pp → p → x. 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 **argvtraps. - Dynamic memory allocation (malloc) — the "return allocated memory" traps.
- Pointer arithmetic — the
pp+1vsp+1stepping trap. - 2-D arrays vs pointer to pointer — the layout edge case.