C fundamentals · lesson 08

Function pointers

Every function lives at an address in the <code>.text</code> segment. You can store that address in a variable, pass it to another function, and call it later. That's a function pointer.

in progress
10 min

Functions have addresses

When the compiler turns your C into machine code, each function becomes a sequence of instructions stored starting at some address in the .text segment. The function name, in most expressions, decays to that address — just like an array name decays to the address of its first element.

c
int add(int a, int b) { return a + b; } printf("%p\n", (void*)add); // prints add's address in .text

Declaring a function pointer

The syntax is notoriously ugly. Read it from the inside out: the name is a pointer, the parentheses specify the argument types, the type before specifies the return type.

c
int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } // fp is a pointer to a function taking two ints, returning int int (*fp)(int, int); fp = add; // store add's address printf("%d\n", fp(3, 4)); // call via the pointer → 7 fp = mul; // switch to mul printf("%d\n", fp(3, 4)); // → 12
💡
Use typedef to make function pointer types readable: typedef int (*BinOp)(int, int); — now BinOp fp = add; reads like a normal variable declaration. Always use typedef in real code.

The real use: callbacks

The most common use of function pointers is passing behavior to a generic function. The C standard library's qsort works this way — it sorts anything because you supply the comparison logic as a function pointer.

c
int cmp_asc(const void *a, const void *b) { return *(int*)a - *(int*)b; // ascending } int cmp_desc(const void *a, const void *b) { return *(int*)b - *(int*)a; // descending } int arr[] = {5, 2, 8, 1, 9}; qsort(arr, 5, sizeof(int), cmp_asc); // {1,2,5,8,9} qsort(arr, 5, sizeof(int), cmp_desc); // {9,8,5,2,1}

Dispatch tables

An array of function pointers is called a dispatch table. It replaces chains of if/else or switch with direct indexing. The kernel uses them everywhere — a system call table is an array of function pointers.

c
typedef int (*Op)(int, int); int add(int a, int b) { return a+b; } int sub(int a, int b) { return a-b; } int mul(int a, int b) { return a*b; } Op ops[] = {add, sub, mul}; // dispatch table int result = ops[opcode](10, 3); // call by index — O(1), no if/else
⚠️
Calling through a NULL function pointer crashes immediately. Always check that a function pointer is non-NULL before calling through it, especially when it comes from user-supplied configuration or a callback registration.
one-line takeaway

A function pointer stores a function's address. Calling through it jumps to that address — the same as any other call.