Code of the Day
AdvancedAdvanced pointers and memory

Function pointers

Store and call functions through pointers in C — syntax, typedef patterns, callbacks, and dispatch tables.

CAdvanced11 min read
By the end of this lesson you will be able to:
  • Declare and use a function pointer variable
  • Pass a function pointer as a callback to another function
  • Use typedef to simplify function pointer type declarations
  • Build a dispatch table (array of function pointers)

Functions in C have addresses, just like variables. A function pointer is a variable that holds the address of a function. Function pointers are how C achieves callbacks, polymorphism (without classes), and event dispatch — patterns you will find throughout the Linux kernel, libc, and every major C library.

Declaring a function pointer

The syntax is notoriously awkward:

int (*fp)(int, int); /* fp is a pointer to a function taking two ints and returning int */

Read from the inside out: fp is a pointer (*fp) to a function ((int, int)) returning int.

#include <stdio.h>

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; }

int main(void) {
    int (*op)(int, int); /* function pointer */

    op = add;
    printf("add(3, 4) = %d\n", op(3, 4)); /* 7 */

    op = sub;
    printf("sub(3, 4) = %d\n", op(3, 4)); /* -1 */

    op = mul;
    printf("mul(3, 4) = %d\n", op(3, 4)); /* 12 */

    return 0;
}

When assigning a function to a function pointer, you can write op = add or op = &add — both work. When calling, op(3, 4) or (*op)(3, 4) — both work. The unambiguous form is without & and without *, which is what modern C code uses.

typedef for clarity

Use typedef to name function pointer types:

typedef int (*BinaryOp)(int, int);

int apply(BinaryOp op, int a, int b) {
    return op(a, b);
}

int main(void) {
    printf("%d\n", apply(add, 5, 3)); /* 8 */
    printf("%d\n", apply(mul, 5, 3)); /* 15 */
    return 0;
}

BinaryOp is now a type — a pointer to a function taking two ints and returning int.

Callbacks

The most common use of function pointers: passing a function to another function so that function can call it at the right time.

The standard library's qsort uses a comparator callback:

#include <stdio.h>
#include <stdlib.h>

int compare_ints(const void *a, const void *b) {
    int ia = *(const int *)a;
    int ib = *(const int *)b;
    return (ia > ib) - (ia < ib); /* returns -1, 0, or 1 */
}

int main(void) {
    int arr[] = {5, 3, 8, 1, 9, 2, 7};
    int n = 7;

    qsort(arr, n, sizeof(int), compare_ints);

    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n"); /* 1 2 3 5 7 8 9 */

    return 0;
}

qsort is declared as:

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

The int (*compar)(const void *, const void *) parameter is a callback. qsort calls it whenever it needs to compare two elements.

Dispatch tables

An array of function pointers implements a fast multi-way dispatch:

#include <stdio.h>

typedef void (*Command)(void);

void cmd_help(void)  { printf("Available commands: help quit status\n"); }
void cmd_quit(void)  { printf("Quitting.\n"); }
void cmd_status(void){ printf("Running normally.\n"); }

struct {
    const char *name;
    Command handler;
} commands[] = {
    {"help",   cmd_help},
    {"quit",   cmd_quit},
    {"status", cmd_status},
    {NULL, NULL}
};

void dispatch(const char *cmd) {
    for (int i = 0; commands[i].name != NULL; i++) {
        if (strcmp(commands[i].name, cmd) == 0) {
            commands[i].handler();
            return;
        }
    }
    printf("Unknown command: %s\n", cmd);
}

This pattern — a table mapping names to handlers — is how command parsers, event loops, and plugin systems work in C.

Function pointers and void pointers

When the return type or argument types need to be generic, function pointers combine with void pointers. The qsort comparator's const void * arguments allow it to sort arrays of any type — the comparator does the casting. This pattern is the foundation of generic programming in C.

Calling through a function pointer with the wrong type is undefined behaviour. The compiler cannot check types through void function pointers. If you store a int (*)(int, int) function as void * and call it through a float (*)(float, float) pointer, the result is undefined. Be careful with the generic void-pointer pattern.

Where to go next

Next: void pointers and generic programming — how void * enables type-erased containers and generic algorithms in C.

Finished reading? Mark it complete to track your progress.

On this page