5.1.26 · D3C Programming

Worked examples — Typedef

2,352 words11 min readBack to topic

See the parent Typedef note for the core idea; here we stress-test it.


The scenario matrix

Think of a typedef line as a small machine: input = an existing type, output = a new name. The behaviour changes depending on what kind of type you feed it and how many variables you declare with the alias. This table lists every distinct case class.

Cell Case class What makes it tricky
A Simple scalar alias (int, unsigned) The trivial base case
B Pointer alias, multiple vars on one line The * is baked in — surprises int* a,b
C struct (tag) alias Drops the struct keyword; tag vs alias namespace
D Anonymous struct + alias No tag exists — alias is the only name
E Array alias Size is baked in; sizing surprises
F Function-pointer alias Ugliest declarator; the payoff case
G Degenerate / zero case: aliasing an alias, typedef void V; Chaining and empty-ish types
H The typedef vs #define twist (exam trap) Text substitution breaks where alias holds
I Real-world word problem Portability across machines
J Self-referential struct (Node) Forward reference — order matters

Every example below is tagged with the cell(s) it covers. Together they hit A–J.


Example 1 — Cell A: the trivial scalar alias

Forecast: Guess before reading — is uint a new type the compiler must invent, or just a label?

  1. Remove typedef: the line reads unsigned int uint;. Why this step? A typedef mirrors a variable declaration exactly, so pretending it is one shows where the new name lands.
  2. Find the "variable name": it is uint. So uint is the alias for unsigned int. Why? Whatever type that pretend-variable would have had is exactly the alias's meaning.
  3. Substitute: uint count = 5; means unsigned int count = 5;. Why? The compiler treats uint and unsigned int as identical — see the figure.
Figure — Typedef

Verify: sizeof(uint) == sizeof(unsigned int) — they are the same type, so the sizes are equal by definition. On a typical 32-bit-int machine that is 4 bytes. No new type was created.


Example 2 — Cell B: pointer alias with multiple variables

Forecast: In plain C, int* a, b; makes only a a pointer. Does the alias behave the same, or differently?

  1. Decode the alias: remove typedefint* IntPtr;IntPtr = "pointer to int". Why? The * binds to the name IntPtr inside the declarator, so the alias is the whole pointer type.
  2. Expand the declaration mentally: IntPtr a, b; = "a is IntPtr, b is IntPtr", i.e. both are int*. Why this step? The alias is one atomic type token. It is not text; the * cannot "fall off" the second variable.
Figure — Typedef

Verify: sizeof(a) == sizeof(b) == sizeof(int*) (both are pointers, typically 8 bytes on a 64-bit machine). Whereas for int* a, b;, sizeof(b) == sizeof(int) (typically 4).


Example 3 — Cell C: struct tag alias

Forecast: Two things are named Point. Does the compiler complain?

  1. Two namespaces: C keeps struct tags in a separate namespace from ordinary identifiers (which includes typedef names). Why this matters? So the tag Point (used as struct Point) and the typedef Point (used bare) can share a spelling without conflict.
  2. The alias: typedef struct Point Point; → remove typedefstruct Point Point; → the pretend variable named Point has type struct Point. Hence bare Pointstruct Point.
  3. Use: Point p; compiles because the compiler resolves the ordinary-identifier Point to the alias. Why? Now you drop the noise word struct everywhere.

Verify: p.x + p.y == 3 + 4 == 7 after assignment — the aliased type has the same members, so field access is unchanged. sizeof(Point) == sizeof(struct Point) (same type).

See Structures in C for the member layout.


Example 4 — Cell D: anonymous struct + alias

Forecast: There is no tag written. Is struct Complex valid?

  1. Decode: remove typedefstruct { double re, im; } Complex; → the variable-name slot holds Complex, whose type is "the anonymous struct". Why? The alias becomes the only handle to this type.
  2. No tag exists: struct Complex w; is a compile error — there is no tag named Complex, only the typedef name. Why this step? Anonymous means "no tag"; the typedef is your sole naming route.

Verify: z.re * z.re + z.im * z.im == 9.0 + 16.0 == 25.0 (this is for ). The struct stores two doubles regardless of naming.


Example 5 — Cell E: array alias (a genuine surprise)

Forecast: The size 3 is baked into the alias. Does sizeof(v) know it?

  1. Decode: remove typedefint Vec3[3]; → pretend variable Vec3 is an array of 3 ints. So the alias means "array of 3 int". Why? The [3] is part of the declarator attached to the name — like the * in Cell B, it is baked into the alias.
  2. Size: Vec3 v; declares 3 ints ⇒ sizeof(v) == 3 * sizeof(int).
  3. Function-parameter twist: if you write void f(Vec3 a), the array-in-parameter decays to int*, so inside f, sizeof(a) == sizeof(int*). The alias does not stop C's array-to-pointer decay. Why show this? This is the degenerate/limiting case where the baked-in size silently disappears.

Verify: On a 4-byte-int machine sizeof(v) == 12, while inside the function sizeof(a) == 8 (pointer). Element sum 1 + 2 + 3 == 6.


Example 6 — Cell F: function-pointer alias (the payoff)

Forecast: Guess the sum of add(2,3) and mul(2,3).

  1. Decode the alias: remove typedefint (*Operation)(int, int); → the parenthesised (*Operation) names a pointer to a function taking two ints and returning int. Why the parentheses? Without them, int *Operation(int,int) would mean "function returning int*". The (*name) forces "pointer to function". See Function Pointers.
  2. Array of aliases: Operation table[2] is an array of 2 such pointers — instantly readable thanks to the alias. Why this step? Without typedef, this would be int (*table[2])(int,int) — the readability win the parent note promised.
  3. Call: table[0](2,3) == add(2,3) == 5; table[1](2,3) == mul(2,3) == 6. So r = 5 + 6.
Figure — Typedef

Verify: r == 11. Units: pure integers, no overflow near INT_MAX.


Example 7 — Cell G: degenerate cases (chaining and void)

Forecast: Does aliasing an alias create a two-step lookup, or does it resolve to int?

  1. Chaining: typedef Integer Age; → decode → Age = Integer = int. Aliases resolve transitively to the underlying type; no chain remains at runtime. Why? Type aliases are fully resolved by the compiler; Age and int are indistinguishable.
  2. Void alias: typedef void V; is perfectly legal — you may then write V *generic; for void *generic; (a generic pointer). But V x; (a variable of void) is illegal, exactly as void x; is. Why this step? The degenerate void case shows the alias inherits every rule of the base type, including its restrictions.

Verify: years == 12, and Age and int satisfy sizeof(Age) == sizeof(int). There is no runtime cost to the chain.


Example 8 — Cell H: the #define vs typedef twist (exam trap)

Forecast: They look symmetric. Are they?

  1. Preprocessor expands line (1): #define is blind text substitution ⇒ INTPTR a, b; becomes int* a, b;. The * attaches to a only ⇒ a is int*, b is int. Why? #define never understands types; it just pastes text before the compiler sees it. See Pointers in C.
  2. Compiler handles line (2): IntPtr is one atomic type ⇒ c and d are both int* (same as Cell B). Why the difference? The alias is a type token; the macro is a string. That is the entire moral.
Figure — Typedef

Verify: sizeof(a) == sizeof(c) == sizeof(d) == sizeof(int*) (pointers), but sizeof(b) == sizeof(int). So exactly 3 of the 4 are pointers.


Example 9 — Cell I: real-world portability word problem

Forecast: Guess: dozens of edits, or one?

  1. Single point of change: because every score is Money, edit one line:
    typedef long long Money;   // new: ~9.2 quintillion range
    Why? Portability was the whole point of the alias — one edit propagates everywhere.
  2. Range check: a signed 64-bit long long holds up to . Why this step? We must confirm the new base type actually covers quintillion.

Verify: , which exceeds . Lines changed: 1. Old int max — indeed too small.


Example 10 — Cell J: self-referential struct for a linked list

Forecast: The alias Node is being defined by this very statement — is it usable inside itself?

  1. Timing: the typedef name Node only becomes visible after the whole typedef completes. Inside the braces it does not exist yet. Why? You cannot refer to a name before its declaration finishes.
  2. Use the tag instead: the tag node (from struct node) is visible as soon as it is written, so struct node *next legally refers to the same type mid-definition. Why this step? Tag namespace availability is what makes self-reference possible — the classic Linked Lists pattern.
  3. After definition: everywhere else you may write Node n; freely.
Figure — Typedef

Verify: A three-node list 1 → 2 → 3 summed by following next gives 1 + 2 + 3 == 6, and traversal terminates when next == NULL. Each Node holds one int + one pointer.


Recall Which cell did each example hit?

Ex1 A ::: scalar alias Ex2 B ::: pointer alias, multiple vars Ex3 C ::: struct tag alias Ex4 D ::: anonymous struct + alias Ex5 E ::: array alias & decay Ex6 F ::: function-pointer alias Ex7 G ::: chained alias + void Ex8 H ::: typedef vs #define Ex9 I ::: portability word problem Ex10 J ::: self-referential struct


Connections