Part of C in 100s

C in 100 Seconds: Typedef | Episode 26

Celest KimCelest Kim

Video: C in 100 Seconds: Typedef — Custom Type Names | Episode 26 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

C typedef: Custom Type Names

typedef int Score; makes Score an alias for int. typedef int (*MathOp)(int, int); makes MathOp an alias for "function pointer." Cleaner type names for clearer code.

typedef introduces a new name for an existing type. It doesn't create a new type — Score and int are interchangeable to the compiler — but the new name makes intent clearer.

The basic shape

#include <stdio.h>

typedef int Score;
typedef char* String;

typedef struct {
  String name;
  Score grade;
} Student;

typedef int (*MathOp)(int, int);

int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }

int main() {
  Score s = 95;
  String msg = "Hello";
  printf("Score: %d\n", s);
  printf("String: %s\n", msg);

  Student alice = {"Alice", 88};
  printf("Student: %s, %d\n", alice.name, alice.grade);

  MathOp op = add;
  printf("add: %d\n", op(3, 4));
  op = mul;
  printf("mul: %d\n", op(3, 4));

  return 0;
}

Four typedefs — primitive alias, pointer alias, struct typedef, function-pointer typedef.

typedef int Score

typedef int Score;
Score s = 95;

Score is now another name for int. Used for documentation:

typedef int UserId;
typedef int Money;
typedef int Score;

UserId u = 42; Money m = 100; reads more meaningfully than int u = 42; int m = 100;. But both are still int to the compiler — u + m compiles without warning.

For real type safety, C doesn't support distinct typedefs. C++ has enum class and other tricks; in C, you'd need a struct wrapper:

typedef struct { int value; } UserId;   // distinct type
UserId u = {42};
// u + m doesn't compile (operator+ undefined for structs)

typedef for pointers

typedef char* String;
String msg = "Hello";

String is char *. The typedef hides the pointer-ness, which can be confusing:

typedef int* IntPtr;
IntPtr a, b;   // a is IntPtr, b is IntPtr — both pointers
// equivalent to: int *a; int *b;
// NOT equivalent to: int *a, b; (which is int *a; int b;)

For non-pointer typedefs the syntax is fine. For pointer typedefs, some style guides advise against — readers don't see the * and might forget to dereference.

typedef struct

typedef struct {
  String name;
  Score grade;
} Student;

Student alice = {"Alice", 88};

Without typedef, you'd write struct { ... } x; or struct Student { ... }; struct Student x;. The typedef'd form is shorter.

For self-referential structs (linked lists, trees), use the named-typedef form:

typedef struct Node {
  int value;
  struct Node *next;   // can refer to "struct Node" inside the typedef
} Node;

The struct Node name is needed for the self-reference; the Node typedef is for everywhere else.

typedef for function pointers

typedef int (*MathOp)(int, int);
MathOp op = add;
op(3, 4);   // 7

MathOp is "pointer to function taking two ints, returning an int." Without typedef, the equivalent is:

int (*op)(int, int) = add;   // direct — readable but verbose
op(3, 4);

For one function pointer, the direct form is fine. For an array, parameter, or multiple uses, the typedef pays off:

// Without typedef
int (*ops[3])(int, int);   // array of 3 function pointers
void apply(int (*op)(int, int), int x, int y);   // function param

// With typedef
MathOp ops[3];
void apply(MathOp op, int x, int y);

The typedef'd version reads almost like English. The raw form is dense.

When typedef helps most

  1. Function pointers. The raw syntax is hard to parse.
  2. Complex types. Arrays of pointers to functions, etc.
  3. Platform-specific types. typedef long file_offset_t; — change one place, update everywhere.
  4. Hiding implementation details. A library exposes OpaqueHandle *h; without revealing what the struct contains.

When typedef hurts

  1. Hiding pointers. String name; doesn't look like a pointer.
  2. Just renaming primitives. typedef int MyInt; saves nothing.
  3. Inconsistent style. Some types typedef'd, others not, no clear rule.

The Linux kernel coding style explicitly discourages unnecessary typedefs. Other codebases use them heavily. Match your project's style.

Typedef and stdlib types

size_t   // sizeof returns this; unsigned int-ish
ptrdiff_t  // pointer subtraction
ssize_t   // signed size_t (POSIX)
time_t    // time() return
FILE      // typedef'd opaque struct (file pointer)
NULL      // (void *)0 (a macro, not a typedef, but similar idea)

Standard typedefs hide platform differences. size_t is unsigned long on some systems, unsigned long long on others — your code uses size_t and works either way.

stdint.h: fixed-width types

#include <stdint.h>

int32_t x = 42;       // exactly 32-bit signed
uint64_t big = 1;      // unsigned 64-bit
uint8_t byte = 0xFF;   // unsigned 8-bit

<stdint.h> typedefs cover specific sizes for portability. Use them for protocols, file formats, anything where the exact size matters.

Common mistakes

Typedef hides pointer. typedef int* IntPtr; IntPtr a;a is a pointer; doesn't look it.

Forgetting struct keyword. struct Student s (without typedef) requires struct. With typedef, just Student s. Easy to mix up.

Cyclic typedefs. typedef struct Node Node; struct Node { Node *next; }; — order matters. struct Node must be declared (even forward) before Node * is used.

Typedef where define would do. typedef int Number; is much heavier than #define Number int. (Both are bad, but typedef carries scope rules; define is just text replacement.)

Not typedef-ing function pointer types. void (*callback)(int, char*) repeated through your codebase — typedef once, use everywhere.

What's next

Episode 27: function pointers — callbacks and dispatch. The MathOp example fully fleshed out.

Recap

typedef existing_type new_name introduces an alias. Doesn't create a new type — just a name. Best uses: function pointers, complex types, platform-portability shims (size_t, int32_t). Less great: hiding pointer-ness or renaming simple primitives. For self-referential structs, use the named-typedef form typedef struct Name { ...; struct Name *next; } Name;.

Next episode: function pointers.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.