Skip to content

C Function Pointers — Callbacks, Dispatch Tables, and Dynamic Calls

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about C Function Pointers. We cover key concepts, practical examples, and best practices to help you master this topic.

C function pointers store the address of a function in a variable, enabling callbacks, dispatch tables, runtime polymorphism, and passing behavior as data to other functions.

What You Will Learn

  • Declaring and using function pointers
  • Passing functions as arguments to other functions (callbacks)
  • Building dispatch tables for command routing
  • Using function pointers with standard library functions (qsort)
  • Function pointer type safety and typedef patterns
  • Arrays of function pointers for state machines

Why It Matters

Function pointers separate behavior from implementation. A sorting function does not need to know how to compare elements -- the caller provides a comparison function pointer. This enables qsort, bsearch, and callback-driven APIs. In Embedded Systems, interrupt handlers are registered through function pointers. In security tools like Durga Antivirus Pro, the scan engine uses a dispatch table of function pointers to route different file types to their specific scan handlers without a giant switch statement.

Real-World Use

A GUI button library stores a function pointer for the click handler. When the user clicks, the library calls the function pointer. The GUI code does not need to know what the handler does -- it could save a file, send a network request, or exit the program. This decoupling allows the library to be reused across projects.

Learning Path

flowchart LR
  A[Inline Functions] --> B[Function Pointers\nYou are here]
  B --> C[setjmp & longjmp]
  style B fill:#f90,color:#fff

Declaring and Using Function Pointers

The syntax for declaring a function pointer matches the function signature, with the pointer name wrapped in parentheses:

#include <stdio.h>

// A regular function
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int main() {
    // Declare a function pointer: takes two ints, returns int
    int (*operation)(int, int);

    // Point to the add function
    operation = add;
    printf("add(10, 5) = %d\n", operation(10, 5));

    // Point to the subtract function
    operation = subtract;
    printf("subtract(10, 5) = %d\n", operation(10, 5));

    return 0;
}

Output:

add(10, 5) = 15
subtract(10, 5) = 5

Typedef for Cleaner Syntax

Function pointer syntax is unwieldy. Use typedef to create readable aliases:

#include <stdio.h>

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

int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int max(int a, int b) { return (a > b) ? a : b; }

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

int main() {
    printf("add: %d\n", apply(add, 10, 5));
    printf("mul: %d\n", apply(multiply, 10, 5));
    printf("max: %d\n", apply(max, 10, 5));
    return 0;
}

Output:

add: 15
mul: 50
max: 10

The typedef line reads: "typedef int (*BinaryOp)(int, int)" -- BinaryOp is a pointer to a function that takes two ints and returns int.

Callbacks with qsort

The standard library's qsort function uses a function pointer for element comparison:

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

// Comparison callback for integers
int compare_int(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
}

// Comparison callback for doubles
int compare_double(const void *a, const void *b) {
    double da = *(const double*)a;
    double db = *(const double*)b;
    if (da < db) return -1;
    if (da > db) return 1;
    return 0;
}

int main() {
    int numbers[] = {42, 7, 15, 8, 99, 23, 1};
    int n = sizeof(numbers) / sizeof(numbers[0]);

    qsort(numbers, n, sizeof(int), compare_int);

    printf("Sorted ints: ");
    for (int i = 0; i < n; i++) printf("%d ", numbers[i]);
    printf("\n");

    double values[] = {3.14, 1.41, 2.72, 0.57, 1.73};
    int m = sizeof(values) / sizeof(values[0]);

    qsort(values, m, sizeof(double), compare_double);

    printf("Sorted doubles: ");
    for (int i = 0; i < m; i++) printf("%.2f ", values[i]);
    printf("\n");

    return 0;
}

Output:

Sorted ints: 1 7 8 15 23 42 99
Sorted doubles: 0.57 1.41 1.73 2.72 3.14

Dispatch Table

An array of function pointers replaces long if-else or switch chains:

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

void cmd_help(void) {
    printf("Available commands: help, add, sub, exit\n");
}

void cmd_add(void) {
    printf("Add command executed\n");
}

void cmd_sub(void) {
    printf("Subtract command executed\n");
}

void cmd_exit(void) {
    printf("Goodbye!\n");
    // Would exit in real code
}

typedef struct {
    const char *name;
    void (*handler)(void);
} Command;

int main() {
    Command commands[] = {
        {"help", cmd_help},
        {"add",  cmd_add},
        {"sub",  cmd_sub},
        {"exit", cmd_exit},
    };
    int n = sizeof(commands) / sizeof(commands[0]);

    char input[64];
    while (1) {
        printf("> ");
        if (fgets(input, sizeof(input), stdin) == NULL) break;

        // Remove newline
        input[strcspn(input, "\n")] = '\0';

        int found = 0;
        for (int i = 0; i < n; i++) {
            if (strcmp(input, commands[i].name) == 0) {
                commands[i].handler();
                found = 1;
                break;
            }
        }

        if (!found) {
            printf("Unknown command. ");
            cmd_help();
        }

        if (strcmp(input, "exit") == 0) break;
    }

    return 0;
}

Output:

> help
Available commands: help, add, sub, exit
> add
Add command executed
> unknown
Unknown command. Available commands: help, add, sub, exit
> exit
Goodbye!

State Machine with Function Pointers

Each state is a function pointer. The state machine calls the current state function, which returns the next state:

#include <stdio.h>
#include <unistd.h>

// Forward declarations
typedef void (*State)(void);

void state_idle(void);
void state_processing(void);
void state_error(void);

static State current = state_idle;

void state_idle(void) {
    printf("[IDLE] Waiting for input...\n");
    // After some condition, transition
    current = state_processing;
}

void state_processing(void) {
    printf("[PROCESSING] Doing work...\n");
    // Simulate work
    current = state_idle;
}

void state_error(void) {
    printf("[ERROR] Something went wrong!\n");
}

int main() {
    for (int i = 0; i < 4; i++) {
        current();
        sleep(1);
    }
    return 0;
}

Output:

[IDLE] Waiting for input...
[PROCESSING] Doing work...
[IDLE] Waiting for input...
[PROCESSING] Doing work...

Function Pointers as Struct Members

Encapsulate behavior in structs for object-oriented patterns:

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

typedef struct {
    char name[64];
    void (*speak)(void);
} Animal;

void dog_speak(void) { printf("Woof!\n"); }
void cat_speak(void) { printf("Meow!\n"); }
void cow_speak(void) { printf("Moo!\n"); }

void animal_introduce(Animal *a) {
    printf("I am %s and I say: ", a->name);
    a->speak();
}

int main() {
    Animal animals[] = {
        {"Rex", dog_speak},
        {"Whiskers", cat_speak},
        {"Bessie", cow_speak},
    };

    for (int i = 0; i < 3; i++) {
        animal_introduce(&animals[i]);
    }

    return 0;
}

Output:

I am Rex and I say: Woof!
I am Whiskers and I say: Meow!
I am Bessie and I say: Moo!

Common Mistakes

  1. Wrong parentheses placement: int *func(int, int) is a function returning an int pointer. int (*func)(int, int) is a pointer to a function. The parentheses matter.

  2. Calling without dereferencing: Both operation(10, 5) and (*operation)(10, 5) work. Modern C allows calling function pointers directly without explicit dereference.

  3. Assigning incompatible function types: The function signature must match the pointer type exactly. int (*op)(int, int) cannot point to a function taking doubles.

  4. Forgetting to check for NULL: Calling a NULL function pointer crashes the program. Always check function pointers before calling: if (callback) callback(data).

  5. Taking address of a function incorrectly: Both operation = add and operation = &add are valid. A function name decays to a pointer automatically.

  6. Using function pointers across translation units with incompatible calling conventions: On some platforms (Windows x86), different calling conventions (cdecl, stdcall) require different function pointer types.

  7. Not using typedef for complex signatures: Without typedef, function pointer types become unreadable: void (*signal(int sig, void (*func)(int)))(int) is the signal function declaration -- a typedef makes this readable.

Practice Questions

  1. What is the difference between int *func() and int (*func)()?
  2. How does qsort use function pointers to sort any data type?
  3. Why would you use a dispatch table instead of a switch statement?
  4. What happens if you call a NULL function pointer?
  5. Challenge: Implement a numerical integration function double integrate(double (*f)(double), double a, double b, int n) that approximates the area under a curve using the trapezoidal rule. Test it with sin(x), cos(x), and x^2.

Mini Project

Build a pluggable sort library:

  • Define typedef int (*Comparator)(const void*, const void*)
  • Implement bubble_sort, insertion_sort, and quick_sort -- all accepting a Comparator callback
  • Implement comparators for ascending int, descending int, and string length
  • Write a benchmark that times each sort on a 10,000-element array with each comparator
  • Allow the user to select sort algorithm and comparator at runtime via command-line arguments
  • Print the sorted array and the time taken for each combination

FAQ

Can function pointers point to inline functions?

Yes, you can take the address of an inline function. However, calls through that pointer cannot be inlined because the target is unknown at compile time.

Are function pointers portable?

Yes, function pointers are standard C. However, casting between function pointers and void pointers is not guaranteed to work and should be avoided.

How do I return a function pointer from a function?

Your function's return type must be the function pointer type. Example: BinaryOp get_operation(char op) returns a function pointer based on the operator character.

What is the size of a function pointer?

Typically 8 bytes on 64-bit systems and 4 bytes on 32-bit systems. The size is the same as data pointers on most platforms.

Can I store function pointers in an array?

Yes, arrays of function pointers are common for dispatch tables and state machines. The type is void (*handlers[])(void) = {func1, func2};

What is Next

Proceed to setjmp and longjmp to learn about non-local jumps for error recovery and Coroutine-like patterns. Then explore Assertions for runtime debugging.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro

Home Browse C