student@ubuntu:~$
Guide

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). Type int, value 42.
  • &*p = p (strip 1, add 1, net zero). Type int *, 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:

  1. Start at the innermost identifier. Look up its declared type.
  2. Walk outward. Every * on the outside strips one * from the type. Every & on the outside adds one.
  3. 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.