What you'll learn
Function pointers let a program choose behavior at runtime without hard-coding every operation. They power standard-library comparators, event handlers, dispatch tables, plugins, and reusable data-structure APIs.
By the end, you'll be able to:
- Declare, assign, and call compatible function pointers
- Pass callbacks into reusable algorithms
- Sort generic arrays safely with
qsort - Carry caller state through an explicit context pointer
Caller
chooses behavior
callbackfunction address
Reusable algorithm
controls when
Function pointer syntax
In int (*operation)(int, int), parentheses makeoperation a pointer to a function accepting two ints and returning int. Without them, the declaration would mean a function returning an int pointer.
#include <stdio.h>
int add(int left, int right) { return left + right; }
int multiply(int left, int right) { return left * right; }
int main(void) {
int (*operation)(int, int) = add;
printf("%d\n", operation(3, 4));
operation = multiply;
printf("%d\n", operation(3, 4));
return 0;
}Note
operation = add and operation = &add work; the shorter form is conventional.Callbacks
A callback is a function supplied to another function. The reusable function controls when to invoke it; the caller controls what behavior runs.
#include <stddef.h>
#include <stdio.h>
void for_each(int values[], size_t count, void (*action)(int)) {
for (size_t i = 0; i < count; i++) {
action(values[i]);
}
}
void print_square(int value) {
printf("%d ", value * value);
}
int main(void) {
int values[] = {1, 2, 3, 4};
for_each(values, 4, print_square);
putchar('\n');
return 0;
}Key idea
Sort with qsort
The standard qsort works with any element type. It receives an array base, element count, element size, and comparator. The comparator receives pointers to two elements and returns negative, zero, or positive.
#include <stdio.h>
#include <stdlib.h>
int compare_ints(const void *left, const void *right) {
int a = *(const int *) left;
int b = *(const int *) right;
return (a > b) - (a < b); // avoids subtraction overflow
}
int main(void) {
int values[] = {40, 10, 30, 20};
size_t count = sizeof values / sizeof values[0];
qsort(values, count, sizeof values[0], compare_ints);
for (size_t i = 0; i < count; i++) printf("%d ", values[i]);
putchar('\n');
return 0;
}Do not compare with a - b
(a > b) - (a < b) is safe for all int values.Readable callback types
Complex pointer declarations become readable when named with typedef. A callback type also gives related APIs one consistent signature.
#include <stdbool.h>
#include <stddef.h>
typedef bool (*Predicate)(int value);
size_t count_if(const int values[], size_t count, Predicate matches) {
size_t matches_count = 0;
for (size_t i = 0; i < count; i++) {
if (matches(values[i])) matches_count++;
}
return matches_count;
}| Declaration | Meaning |
|---|---|
bool test(int) | A function |
bool (*test)(int) | A variable pointing to such a function |
typedef bool (*Predicate)(int) | A reusable pointer-type alias |
Dispatch tables
An array of function pointers maps an index or state to behavior. This replaces a large switch when every operation has one signature.
#include <stdio.h>
typedef void (*Command)(void);
void create(void) { puts("create"); }
void save(void) { puts("save"); }
void quit(void) { puts("quit"); }
int main(void) {
Command commands[] = {create, save, quit};
enum { CREATE, SAVE, QUIT };
int selected = SAVE;
if (selected >= CREATE && selected <= QUIT) {
commands[selected]();
}
return 0;
}Watch out
Context pointers
C has no closures that automatically capture local state. Callback APIs often accept an additional void * context, which the caller owns and the callback converts back to the documented type.
#include <stddef.h>
#include <stdio.h>
typedef void (*Visitor)(int value, void *context);
void visit(const int values[], size_t count, Visitor visitor, void *context) {
for (size_t i = 0; i < count; i++) {
visitor(values[i], context);
}
}
void add_to_total(int value, void *context) {
int *total = context;
*total += value;
}
int main(void) {
int values[] = {5, 10, 15};
int total = 0;
visit(values, 3, add_to_total, &total);
printf("%d\n", total);
return 0;
}Tip
Interface safety
- Check nullable callback pointers before calling
- Use one exact signature and a typedef for related callbacks
- Never cast an incompatible function just to silence the compiler
- Keep callback and context lifetimes valid for the entire registration or call
- Document whether re-entry or concurrent invocation is possible
Recap & quick check
Key takeaways
- A function pointer stores the address of a function with a compatible signature.
- Callbacks separate an algorithm's control flow from caller-selected behavior.
- qsort comparators return negative, zero, or positive and must avoid overflow.
- typedef makes callback contracts readable and consistent.
- A void context pointer carries explicit caller state without relying on globals.
Quick check
1. Why are parentheses required in int (*operation)(int)?
2. What must a qsort comparator return for equal elements?
3. What problem does a context pointer solve?
4. Is casting between incompatible function-pointer signatures safe?
Phase 3 complete. Phase 4 begins with Module 15 — Bitwise Programming & Data Representation, where C works directly with bits and byte layouts.