5.1.26 · D2C Programming

Visual walkthrough — Typedef

1,659 words8 min readBack to topic

Step 1 — What a plain declaration really says

WHAT. Start with the simplest possible line of C:

int x;

WHY start here. typedef copies this exact shape. If we understand how the eye and the compiler read a variable declaration, we get typedef for free — because typedef is literally this line with one word glued on the front.

PICTURE. A declaration in C has two jobs pinned to two locations. On the left sits the base type (int). In the middle sits the name being introduced (x). Read it as a sentence:

  • int — the kind of thing.
  • x — the label we can use to refer to a thing of that kind.
Figure — Typedef

Step 2 — Glue typedef on the front

WHAT. Put the keyword typedef in front of the same line:

typedef int Integer;

WHY. The C designers made a deliberate choice: instead of inventing brand-new syntax for aliases, they reused the declaration syntax you already know. The only rule they added is one flip of meaning, shown next.

PICTURE. Everything looks identical to Step 1, except a word appears on the far left:

  • typedef — a switch. It tells the compiler: "do not create a variable; instead, whatever name sits in the name slot becomes a new name for a type."
  • int — still the base type.
  • Integer — sits in the name slot. So Integer is now an alias for int.
Figure — Typedef

Step 3 — Why the * sticks to the name, not the type

WHAT. Now a line with a pointer:

char *String;   // without typedef

WHY this matters. This is the single trickiest thing in all of C declarations, and the whole "pointer typedef surprise" flows from it. We must see where the * binds before we can trust any pointer alias.

PICTURE. The * is not part of the base type char. It is glued to the name String. Read the declaration as: "*String is a char", i.e. "when you dereference String, you get a char", i.e. String is a pointer to char.

  • char — base type (what you get after following the pointer).
  • * — binds to the name, meaning "pointer to...".
  • String — the name.
Figure — Typedef

Step 4 — Add typedef to the pointer line

WHAT.

typedef char *String;

WHY. Apply Steps 2 and 3 together. Glue typedef on; the name slot's identifier is String; the type that identifier would have had is "pointer to char". So String becomes an alias for char *.

PICTURE. Same layout as Step 3, with the switch flipped:

Because the * was baked into the alias in Step 3, it is now part of the type String names. This is the seed of the famous surprise in Step 5.

Figure — Typedef

Step 5 — The multi-name surprise (edge case)

WHAT. Declare two names with a pointer alias vs. a raw *:

typedef int* IntPtr;
IntPtr  a, b;   // a: int*   b: int*   (BOTH pointers)
 
int*    c, d;   // c: int*   d: int    (only c!)

WHY show this. This is the degenerate case where beginners are burned. We must prove why the results differ — and the answer is entirely from Step 3.

PICTURE. In int* c, d; the * binds to the first name only (c); d reuses just the base type int. But IntPtr is one whole type — the * is already inside it — so every name in the list inherits the complete pointer type.

  • top line: the arrow shows the * reaching only c.
  • bottom line: no loose * exists — the pointerness lives inside IntPtr, so both a and b are full pointers.
Figure — Typedef

Step 6 — The struct case: dropping a keyword

WHAT.

struct Point { int x, y; };
typedef struct Point Point;   // alias 'Point' for 'struct Point'
Point p;                      // instead of 'struct Point p;'

WHY. In C, struct tags live in a separate namespace, so the full type name is two words: struct Point. Apply our rule: remove typedefstruct Point Point; declares a variable named Point of type struct Point. So the alias Point now means struct Point, and we may drop the keyword forever.

PICTURE. Two namespaces sit side by side; the typedef builds a bridge from the plain name Point (type namespace) to the tagged type struct Point.

Figure — Typedef

Step 7 — The boss: a function-pointer typedef

WHAT. Decode the ugliest common form:

typedef int (*Operation)(int, int);

WHY. Function-pointer syntax scares everyone. But our rule still works — the name slot is buried in the middle, and finding it is the whole game. See Function Pointers.

PICTURE. Remove typedef, then hunt the name slot. The parentheses (*Operation) group the * with the name (forcing "pointer to function", not "function returning pointer"). Reading outward from the name:

  • Operation — the name slot ⇒ this becomes the alias.
  • (* ... ) — pointer, parenthesised so it binds before the call parens.
  • (int, int) — the parameters of the function.
  • int (far left) — the function's return type.

So Operation = "pointer to a function taking two ints and returning int." Usage:

int add(int a, int b){ return a + b; }
Operation op = add;
int r = op(2, 3);   // r = 5
Figure — Typedef

The one-picture summary

WHAT. One flow that compresses every step: cover typedef, read as a variable declaration, the name slot is your alias, whatever type that name would have had is the alias's meaning — and the * always rides with the name.

Figure — Typedef

cover the word typedef

find the identifier being declared

the name slot becomes

the type that variable would have had

asterisk rides with the name

so a list of names

Any typedef line

Read as a variable declaration

Name slot found

The new type alias

Meaning of the alias

Pointerness baked into alias

All names get the full type

Recall Feynman: retell the whole walkthrough

A normal C line, like int x;, has two slots: a type on the left and a name somewhere in the middle. Put the magic word typedef in front and you flip one switch: the name in that middle slot is no longer a variable — it becomes a new name for a type, and the type it would have had is what the name now means. The trickiest part is the little star *: it always sticks to the name, not the base type. That's why typedef int* IntPtr; makes IntPtr a, b; give you two pointers, while int* a, b; gives only one. Structs need two words (struct Point), so a typedef bridges to a one-word name. And even the scary function-pointer form obeys the exact same rule: dig out the buried name, and that name is your alias. One trick, every case.


Connections