Deep Dive: Pointer Type Algebra
& and * as inverse operators on types: a mechanical rule that turns pointer questions into arithmetic
Based on content from Dr. Stu Steiner, Eastern Washington University.
This page is optional reading. Pointer Basics, Pass-by-Pointer, and Double Pointers teach you the syntax and the rules. This page teaches you one mechanical trick that makes every pointer expression readable and every Quiz 2 “what is the type and value?” question solvable without guessing.
The trick, in one sentence
Treat & and * as inverse operators on types:
& adds one * to the type.
* strips one * from the type.
That is the entire rule. Every pointer expression in C can be analyzed by applying the rule step by step to the type.
If you end up asking the compiler to strip a * off a type that has none, you have hit a type error. If you end up asking it to read a * that is attached to the wrong type, you have hit a type mismatch. Both failures are mechanical and predictable once you see them as arithmetic.
Worked examples
All of these use the setup:
int x = 42; /* x has type int, value 42 */
int *p = &x; /* p has type int *, value &x */
int **pp = &p; /* pp has type int **, value &p */
Stripping with *
| Expression | Start type | Apply * |
End type | What it reads |
|---|---|---|---|---|
*p |
int * |
strip 1 | int |
the object p points at (x, value 42) |
*pp |
int ** |
strip 1 | int * |
the pointer pp points at (p, value &x) |
**pp |
int ** |
strip 2 | int |
follow two arrows; lands on x, value 42 |
Adding with &
| Expression | Start type | Apply & |
End type | What it is |
|---|---|---|---|---|
&x |
int |
add 1 | int * |
the address of x |
&p |
int * |
add 1 | int ** |
the address of p |
&pp |
int ** |
add 1 | int *** |
the address of pp |
Canceling
If an expression is legal, & and * cancel:
*&x=x(add 1, strip 1, net zero). Typeint, value42.&*p=p(strip 1, add 1, net zero). Typeint *, value&x.
*&42 is not legal. & requires an lvalue (something with a storage location). 42 is a computed value, not storage, so &42 is a type error. Mechanical again: the rule applies to types, and 42’s type has no slot on which & can latch.
The Quiz 2 rubric, restated
Every pointer expression you will see on a C quiz (or in a real bug report) can be graded as a pair: its type and its value. “Address” is not a type. “Pointer” is not a type. The type is always a concrete C type ending in some number of stars.
The procedure:
- Start at the innermost identifier. Look up its declared type.
- Walk outward. Every
*on the outside strips one*from the type. Every&on the outside adds one. - When you have the final type, compute the value by following whatever arrows it implies.
Write every answer as type | value. You will not lose points to vocabulary.
Diagnosing type errors
Applying the rule also makes errors readable.
Stripping a * off a non-pointer
int x = 10;
int y = *x; /* x has type int; strip 1 * → nothing to strip */
Error: *x asks the compiler to follow x as if it were an address, but x’s type has zero stars. The compiler rejects it with “invalid type argument of unary *”.
Assigning across mismatched pointer depths
int x = 10;
int *p = x; /* RHS is type int; LHS needs int * */
int *q = &&x; /* &&x would need int to be a pointer */
Error: the types on the two sides of = do not match after applying & or *. Line 2 needs int *, but x is int; add & to fix (int *p = &x;). Line 3 tries to apply & twice, and the first application already made it int *, so the second would require int * to be a stored object, which it is not (&x is a temporary value).
Forgetting & in scanf
int age;
scanf("%d", age); /* passes the value of age; scanf needs the address */
The type-algebra view: scanf reads %d and expects its next argument to be int *. You passed int. Mismatch: one level too shallow. Fix by adding one &: scanf("%d", &age);.
This is why the & rule for scanf is not a special case. Every variadic function that writes into a caller’s variable needs one more level of indirection than a function that just reads the value. & is how you deliver it.
Reading messy declarations
The type-algebra rule applies to declarator syntax too, with one adjustment: read the declarator right-to-left, applying * as “pointer to” each time.
| Declaration | Reading | Final type |
|---|---|---|
int x |
plain int |
int |
int *p |
p is a pointer to int |
int * |
int **pp |
pp is a pointer to a pointer to int |
int ** |
int ***ppp |
pointer to pointer to pointer to int |
int *** |
char **argv |
argv is a pointer to a pointer to char |
char ** |
For declarators with array brackets and function parens mixed in, use the full right-left rule: start at the identifier, read right first (arrays/functions), then left (pointers), respecting parentheses. That level of complication is rare in CSCD 240; the typed-stars-count rule handles everything you will see until int (*cmp)(const void *, const void *) shows up in week 7.
Practice: compute the type and value
Given:
int a = 7; /* stored at 0x100 */
int b = 9; /* stored at 0x104 */
int *p = &a; /* stored at 0x200 */
int **pp = &p; /* stored at 0x300 */
Fill in the type and value for each expression. Cover the answer column and try each on your own first.
| Expression | Type | Value |
|---|---|---|
a |
int |
7 |
p |
int * |
0x100 |
*p |
int |
7 |
&b |
int * |
0x104 |
pp |
int ** |
0x200 |
*pp |
int * |
0x100 (same as p) |
**pp |
int |
7 |
&pp |
int *** |
0x300 |
*&p |
int * |
0x100 (same as p, inverses cancel) |
&*p |
int * |
0x100 (same as p, inverses cancel) |
If you got every row without looking, you have the algebra. If you missed one, re-derive it by applying the rule one operator at a time.
Why this helps
Pointer mistakes feel mysterious when you reason about them in English (“the pointer to the pointer to the int”). They stop being mysterious when you reason about them in types (int ** → int * → int). Compiler errors become predictable: they are always telling you that some expression’s type is wrong by exactly one level. Quiz answers become mechanical: two columns, two rules.
Once the rule is automatic, you can put it down and stop thinking about it. That is the point. For the mechanical picture of how addresses themselves work (stack slots, virtual memory, what &x resolves to in the ELF), see the machine model deep dive.