C Function Pointers — Callbacks, Dispatch Tables, and Dynamic Calls
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
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.Calling without dereferencing: Both
operation(10, 5)and(*operation)(10, 5)work. Modern C allows calling function pointers directly without explicit dereference.Assigning incompatible function types: The function signature must match the pointer type exactly.
int (*op)(int, int)cannot point to a function taking doubles.Forgetting to check for NULL: Calling a NULL function pointer crashes the program. Always check function pointers before calling:
if (callback) callback(data).Taking address of a function incorrectly: Both
operation = addandoperation = &addare valid. A function name decays to a pointer automatically.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.
Not using typedef for complex signatures: Without typedef, function pointer types become unreadable:
void (*signal(int sig, void (*func)(int)))(int)is thesignalfunction declaration -- a typedef makes this readable.
Practice Questions
- What is the difference between
int *func()andint (*func)()? - How does
qsortuse function pointers to sort any data type? - Why would you use a dispatch table instead of a switch statement?
- What happens if you call a NULL function pointer?
- 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, andquick_sort-- all accepting aComparatorcallback - 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
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.