Nearby lessons

107 of 124

C - typedef

typedef creates a new name for an existing type. Learn how it removes the struct keyword, tames pointer and function-pointer declarations, documents intent — and when it hides more than it helps.

The Basic Syntax

Write typedef, then the existing type, then the new name. It reads like a variable declaration with typedef bolted on the front — because that is exactly how the grammar works:

Example01
CCode Cell
1#include <stdio.h>
2 
3typedef unsigned int uint; /* uint means unsigned int */
4typedef long long i64;
5typedef unsigned char byte;
6 
7int main()
8{
9 uint count = 100;
10 i64 big = 9000000000LL;
11 byte flags = 0xFF;
12 
13 printf("count = %u (%zu bytes)\n", count, sizeof(uint));
14 printf("big = %lld (%zu bytes)\n", big, sizeof(i64));
15 printf("flags = %u (%zu byte)\n", flags, sizeof(byte));
16 return 0;
17}
Output
count = 100  (4 bytes)
big   = 9000000000 (8 bytes)
flags = 255  (1 byte)

It Is an Alias, Not a New Type

In simple words: a typedef name and the original type are the same type with two spellings. The compiler will not stop you mixing them, and no new conversion rules appear. If you want genuine type safety, wrap the value in a one-member structure instead.
Example02
CCode Cell
1#include <stdio.h>
2 
3typedef int Metres;
4typedef int Seconds;
5 
6int main()
7{
8 Metres distance = 100;
9 Seconds time = 20;
10 int plain = 5;
11 
12 /* All three are just int - no warning for any of these */
13 distance = time;
14 time = plain;
15 
16 printf("distance = %d, time = %d\n", distance, time);
17 printf("Metres and Seconds are the same type to the compiler.\n");
18 return 0;
19}
Output
distance = 20, time = 5
Metres and Seconds are the same type to the compiler.

The Main Use — Structures

C requires struct Student s;. A typedef lets you write Student s;, which is why almost every real codebase uses one:

Example03
CCode Cell
1#include <stdio.h>
2 
3/* Without typedef */
4struct Point1 { int x, y; };
5 
6/* With typedef - anonymous struct */
7typedef struct { int x, y; } Point2;
8 
9/* With typedef - tag kept as well */
10typedef struct Point3 { int x, y; } Point3;
11 
12int main()
13{
14 struct Point1 a = {1, 2}; /* struct keyword required */
15 Point2 b = {3, 4}; /* cleaner */
16 Point3 c = {5, 6};
17 struct Point3 d = {7, 8}; /* the tag still works */
18 
19 printf("(%d,%d) (%d,%d) (%d,%d) (%d,%d)\n",
20 a.x, a.y, b.x, b.y, c.x, c.y, d.x, d.y);
21 return 0;
22}
Output
(1,2) (3,4) (5,6) (7,8)

Keep the Tag for Self-Reference

Inside its own braces the typedef name does not exist yet. A linked-list node needs the tag:

Example04
CCode Cell
1#include <stdio.h>
2 
3/* BROKEN
4typedef struct {
5 int data;
6 Node *next; <- Node is not defined yet
7} Node;
8*/
9 
10typedef struct Node {
11 int data;
12 struct Node *next; /* refer to it by its tag */
13} Node;
14 
15int main()
16{
17 Node c = {3, NULL};
18 Node b = {2, &c};
19 Node a = {1, &b};
20 
21 for (Node *p = &a; p != NULL; p = p->next)
22 printf("%d -> ", p->data);
23 printf("NULL\n");
24 return 0;
25}
Output
1 -> 2 -> 3 -> NULL

With Unions and Enums

The same trick removes the union and enum keywords:

Example05
CCode Cell
1#include <stdio.h>
2 
3typedef union {
4 int i;
5 float f;
6 char bytes[4];
7} Value;
8 
9typedef enum { RED, GREEN, BLUE } Colour;
10 
11int main()
12{
13 Value v;
14 Colour c = GREEN;
15 
16 v.i = 65;
17 printf("As int : %d\n", v.i);
18 printf("As char : %c\n", v.bytes[0]);
19 
20 printf("Colour : %d\n", c);
21 printf("sizeof(Value) = %zu\n", sizeof(Value));
22 return 0;
23}
Output
As int  : 65
As char : A
Colour  : 1
sizeof(Value) = 4

Taming Function Pointers

This is where typedef earns its place. Compare the raw declaration with the aliased one:

Example06
CCode Cell
1#include <stdio.h>
2 
3typedef int (*BinaryOp)(int, int); /* alias for the ugly type */
4 
5int add(int a, int b) { return a + b; }
6int multiply(int a, int b) { return a * b; }
7 
8/* Without the typedef this parameter list is barely readable:
9 int apply(int (*op)(int, int), int a, int b) */
10int apply(BinaryOp op, int a, int b)
11{
12 return op(a, b);
13}
14 
15int main()
16{
17 BinaryOp ops[2] = {add, multiply};
18 const char *names[2] = {"add", "multiply"};
19 int i;
20 
21 for (i = 0; i < 2; i++)
22 printf("%-8s(6, 7) = %d\n", names[i], apply(ops[i], 6, 7));
23 return 0;
24}
Output
add     (6, 7) = 13
multiply(6, 7) = 42

The New Name Goes Where the Variable Would

For array and pointer types, the alias name sits in the middle — exactly where a variable name would sit in an ordinary declaration:

Example07
CCode Cell
1#include <stdio.h>
2 
3typedef int Matrix[3][3]; /* Matrix is a 3x3 int array */
4typedef char Line[80]; /* Line is an 80-char buffer */
5typedef int *IntPtr; /* IntPtr is a pointer to int */
6 
7int main()
8{
9 Matrix m = {{1,2,3}, {4,5,6}, {7,8,9}};
10 Line text = "Hello";
11 int x = 42;
12 IntPtr p = &x;
13 int i, j;
14 
15 for (i = 0; i < 3; i++)
16 {
17 for (j = 0; j < 3; j++) printf("%3d", m[i][j]);
18 printf("\n");
19 }
20 
21 printf("text = %s (%zu bytes)\n", text, sizeof(Line));
22 printf("*p = %d\n", *p);
23 return 0;
24}
Output
  1  2  3
  4  5  6
  7  8  9
text = Hello (80 bytes)
*p   = 42

The Pointer-Typedef Trap

A typedef that hides a pointer surprises everyone who reads it. With typedef int *IntPtr;, the declaration IntPtr a, b; makes both pointers — which is the opposite of int *a, b;. Worse, const IntPtr p means int *const p (a const pointer), not const int *p. Most style guides ban pointer typedefs for exactly this reason.
Example08
CCode Cell
1#include <stdio.h>
2 
3typedef int *IntPtr;
4 
5int main()
6{
7 int x = 10, y = 20;
8 
9 IntPtr a = &x, b = &y; /* BOTH are pointers */
10 int *c = &x, d = 99; /* c is a pointer, d is an int */
11 
12 printf("*a=%d *b=%d\n", *a, *b);
13 printf("*c=%d d=%d\n", *c, d);
14 
15 const IntPtr p = &x; /* means: int *const p */
16 *p = 50; /* allowed - the TARGET is not const */
17 /* p = &y; ERROR - the POINTER is const */
18 
19 printf("x is now %d\n", x);
20 return 0;
21}
Output
*a=10 *b=20
*c=10  d=99
x is now 50

Portable Fixed-Width Types

The standard library already ships the typedefs you most often want. Prefer <stdint.h> to rolling your own:

TypeMeaningHeader
int32_t, uint8_tExactly that many bits<stdint.h>
size_tA size or count; never negative<stddef.h>
ptrdiff_tThe difference of two pointers<stddef.h>
FILEA file stream<stdio.h>
time_tA calendar time<time.h>
Example09
CCode Cell
1#include <stdio.h>
2#include <stdint.h>
3 
4int main()
5{
6 int8_t small = 127;
7 uint16_t port = 8080;
8 int32_t count = 2000000000;
9 uint64_t huge = 18000000000000000000ULL;
10 
11 printf("int8_t : %d (%zu byte)\n", small, sizeof(int8_t));
12 printf("uint16_t : %u (%zu bytes)\n", port, sizeof(uint16_t));
13 printf("int32_t : %d (%zu bytes)\n", count, sizeof(int32_t));
14 printf("uint64_t : %llu (%zu bytes)\n",
15 (unsigned long long) huge, sizeof(uint64_t));
16 return 0;
17}
Output
int8_t   : 127 (1 byte)
uint16_t : 8080 (2 bytes)
int32_t  : 2000000000 (4 bytes)
uint64_t : 18000000000000000000 (8 bytes)

typedef vs #define

They look similar and behave very differently. #define is blind text substitution; typedef is understood by the compiler:

typedef#define
Handled byThe compilerThe preprocessor
Respects scopeYesNo — from the line onward
Works for pointersCorrectlyBreaks on multiple declarators
Can alias arraysYesNo
Needs a semicolonYesNo
Example10
CCode Cell
1#include <stdio.h>
2 
3typedef int *TypedefPtr;
4#define DEFINE_PTR int *
5 
6int main()
7{
8 int x = 1, y = 2;
9 
10 TypedefPtr a, b; /* both are int * */
11 DEFINE_PTR c, d; /* expands to: int *c, d; -> d is an int! */
12 
13 a = &x; b = &y; c = &x;
14 d = 99; /* proof that d is a plain int */
15 
16 printf("*a=%d *b=%d *c=%d d=%d\n", *a, *b, *c, d);
17 printf("sizeof b = %zu, sizeof d = %zu\n", sizeof(b), sizeof(d));
18 return 0;
19}
Output
*a=1 *b=2 *c=1 d=99
sizeof b = 8, sizeof d = 4

Common Mistakes

  • Reversing the order — it is typedef int Metres;, never typedef Metres int;.
  • Expecting type safety — two typedefs of int are freely interchangeable.
  • Forgetting the semicolon — a typedef is a declaration and needs one.
  • Self-reference in an anonymous struct — keep the tag for linked structures.
  • Hiding pointersconst MyPtr p almost never means what the reader expects.
  • Aliasing an already-clear typetypedef int Integer; adds a word to learn and no information.
Trainer's Note: use typedef when it removes noise the reader does not need — struct keywords, function-pointer syntax, platform-specific widths. Avoid it when it removes information the reader does need, which is nearly always the case for pointers.
📝 Key Takeaways
  • typedef existingType newName; — the new name comes last.
  • It creates an alias, never a new type; no conversion rules change.
  • typedef struct {...} Name; lets you drop the struct keyword.
  • It makes function-pointer declarations readable.
  • Hiding a pointer behind a typedef often causes more confusion than it saves.

🧠 Test Your Knowledge

4 Questions
Progress: 0 / 4