A function pointer stores the address of a function. You can call the function through the pointer, pass it to other functions as a callback, or store it in a struct to get something resembling object methods. The syntax is awkward, but the concept is straightforward once you understand how pointers work in C.

Basic Syntax

#include <stdio.h>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

int main() {
    /* Declare a pointer to a function taking two ints, returning int */
    int (*op)(int, int);

    op = add;              /* assign the address of add */
    printf("%dn", op(10, 3));  /* call through the pointer: 13 */

    op = sub;
    printf("%dn", op(10, 3));  /* 7 */

    return 0;
}

Read int (*op)(int, int) as: op is a pointer (*op) to a function that takes two int parameters and returns an int.

The parentheses around *op are required. Without them, int *op(int, int) declares a function named op that returns int* — not a pointer to a function.

typedef Makes the Syntax Readable

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

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

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

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

Callbacks — Passing Functions to Functions

The C standard library uses function pointers for qsort and bsearch. You provide a comparison function:

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

int compare_asc(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}

int compare_desc(const void *a, const void *b) {
    return (*(int*)b - *(int*)a);
}

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

    qsort(arr, n, sizeof(int), compare_asc);
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("n");   /* 1 2 3 5 8 9 */

    qsort(arr, n, sizeof(int), compare_desc);
    for (int i = 0; i < n; i++) printf("%d ", arr[i]);
    printf("n");   /* 9 8 5 3 2 1 */

    return 0;
}

Dispatch Tables — Switch Without switch

#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 divide(int a, int b) { return b ? a / b : 0; }

typedef int (*Op)(int, int);

int main() {
    Op ops[] = { add, sub, mul, divide };
    char *names[] = { "add", "sub", "mul", "div" };

    int a = 20, b = 4;
    for (int i = 0; i < 4; i++) {
        printf("%s(%d, %d) = %dn", names[i], a, b, ops[i](a, b));
    }

    return 0;
}

An array of function pointers lets you select behaviour with an index — cleaner than a large switch when the operations share the same signature.

Function Pointers in Structs — Simulating Methods

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

typedef struct {
    char name[32];
    int  score;
    void (*print)(const void *self);
} Student;

void student_print(const void *self) {
    const Student *s = (const Student*)self;
    printf("%-15s %dn", s->name, s->score);
}

Student *student_new(const char *name, int score) {
    Student *s = malloc(sizeof(Student));
    strncpy(s->name, name, 31);
    s->score = score;
    s->print  = student_print;
    return s;
}

int main() {
    Student *s = student_new("Alice", 94);
    s->print(s);   /* Alice           94 */
    free(s);
    return 0;
}

This is how C-based object systems (like GTK’s GObject and the Linux kernel’s file_operations struct) implement polymorphism — structs containing function pointers that serve as virtual method tables.

Common Mistakes

/* 1. Calling a NULL function pointer — segfault */
int (*op)(int, int) = NULL;
op(5, 3);   /* crash */

/* Fix: always check before calling */
if (op != NULL) op(5, 3);

/* 2. Wrong signature — undefined behaviour */
int func(int a) { return a; }
int (*ptr)(int, int) = func;   /* signature mismatch */
ptr(5, 3);   /* undefined — stack likely corrupted */

Calling through a NULL function pointer produces a dangling pointer-style crash — the call jumps to address 0, which is not mapped. Always initialise function pointers and check them before calling if they might be unset.

TL;DR

  • Syntax: return_type (*name)(param_types)
  • Use typedef to make the syntax readable
  • Use for callbacks, dispatch tables, and polymorphic structs
  • Check for NULL before calling a function pointer that might be unset
  • Signature must match exactly — wrong signatures cause undefined behaviour
  • The C standard library uses function pointers in qsort, bsearch, signal