Function Pointers and Callbacks
This chapter covers pointers to functions and their primary use case: callbacks. Function pointers are how C implements polymorphism, strategy patterns, and event-driven designs.
Why This Matters
Function pointers let you pass behavior as data. Sorting with a custom comparator, registering event handlers, and building plugin systems all rely on them. They are also the gateway to understanding how C APIs achieve extensibility.
Prerequisites
c.core.19— pointer basics.c.core.26— structs (for callback contexts).
Core Concept
A function pointer stores the address of a function. Its type encodes the function's signature (parameter types and return type).
int add(int a, int b) { return a + b; }
int (*op)(int, int); /* op is a pointer to function (int,int)->int */
op = add; /* function name decays to function pointer */
op = &add; /* equivalent */
int r = op(2, 3); /* call through the pointer */
The function name add decays to a function pointer in most contexts, and &add explicitly takes its address; both are equivalent.
Syntax
Declaration
return_type (*name)(parameter_types);
The parentheses around *name are required. Without them:
int *f(int); /* function returning int* (NOT a function pointer) */
int (*f)(int); /* pointer to function (int)->int */
Callback typedef
typedef int (*compare_fn)(const void *, const void *);
Calling
result = fp(args); /* call through pointer */
result = (*fp)(args); /* equivalent (dereference is optional) */
Examples
Sorting with a comparator (callback)
#include <stdlib.h>
int compare_int(const void *a, const void *b)
{
const int *ia = a;
const int *ib = b;
return (*ia > *ib) - (*ia < *ib);
}
int main(void)
{
int arr[] = {5, 3, 1, 4, 2};
qsort(arr, 5, sizeof arr[0], compare_int);
return 0;
}
qsort takes a callback (compare_int) that it calls to compare elements. The callback receives void * arguments because qsort is generic.
Callback with user data (context)
typedef void (*event_handler)(void *ctx, int event);
void notify(event_handler h, void *ctx, int event)
{
h(ctx, event);
}
Passing ctx (often a void *) lets the callback access its own state without global variables.
Table of function pointers (dispatch table)
typedef int (*op_fn)(int, int);
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
op_fn ops[] = { add, sub };
int main(void)
{
return ops[0](10, 5); /* add -> 15 */
}
How It Works
A function pointer is a pointer value holding the address of the function's code. Calling it jumps to that address with the arguments per the ABI. The type encodes the signature so the compiler can check calls and generate correct argument passing.
Variations
Function pointers vs. data pointers
In ISO C, function pointers are a distinct category from object pointers. They do not portably convert to void *. Converting between function pointers and object pointers is not guaranteed. VERIFIED
Callback registries
A struct can hold a callback and its context:
struct Handler {
void (*fn)(void *);
void *ctx;
};
Common Mistakes
- Writing
int *f(int)when you meanint (*f)(int). - Forgetting the parentheses in the declarator.
- Converting a function pointer to
void *(not portable). - Calling a NULL function pointer (UB).
Undefined Behavior
- Calling a NULL function pointer.
VERIFIED - Calling a function through a pointer of an incompatible function type.
VERIFIED
- Converting a function pointer to an object pointer and back (not portable;
may lose information).
Portability
- Function pointer declaration and calling syntax are standard.
- Function pointer ↔
void *conversion is not portable (POSIXdlsymis a
notable platform-specific exception).
Under the Hood
A function pointer compiles to the address of the function's entry point. A call through it is an indirect call (call *reg on x86). Indirect calls are slightly slower than direct calls due to branch-target prediction, and they interact with CFI (c.sec.cfi).
Practical Usage
- Pass comparators to sorting/search functions.
- Register event handlers and callbacks with a context pointer.
- Build dispatch tables for state machines and command handlers.
- Use function pointers for plugin architectures.
Exercises
1. Declare a function pointer for a double(double) function and call it. 2. Write a map function that applies a callback to each element of an array. 3. Build a dispatch table mapping strings to functions and look up a command. 4. Demonstrate that fp(args) and (*fp)(args) both work.
Deep Challenge
Design a small plugin system using a struct that holds a name, a function pointer, and a context pointer, plus a registration function and a dispatcher. Explain the ownership and lifetime of the context pointer and how you would make the system type-safe.
Related Concepts
c.typedef.fnptr— function-pointer typedefs.c.core.24— void* and callbacks.c.sec.cfi— control-flow integrity.
References
- ISO/IEC 9899:2018 §6.3.2.3 (function pointer conversions), §6.5.2.2
(function calls), §6.7.6.3 (function declarators).
Verification
- Function name decays to function pointer.
VERIFIED fp(args)and(*fp)(args)are equivalent.VERIFIED- Function pointers do not portably convert to void*.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Function pointer declaration
- [ ] Function name decay
- [ ] Calling through a function pointer
- [ ] Callback with context
- [ ] Dispatch tables
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.func.fnptr | 0 | 6 |
| c.func.callback | 0 | 6 |