Callback functions are a cornerstone of modular, flexible, and decoupled C programs. To fully understand callbacks, you must first master the true “soul” of C: pointers, and more specifically, function pointers.
Callbacks are widely used in operating systems, embedded firmware, libraries, and event-driven architectures where behavior must be customized without modifying core logic.
🧠 Function Pointers #
While pointers are commonly used to reference data, function pointers reference executable code stored in memory.
Definition #
The general syntax for declaring a function pointer is:
ReturnType (*PointerName)(ParameterList);
The parentheses around (*PointerName) are mandatory. Without them, the declaration would instead describe a function returning a pointer.
Improving Readability with typedef
#
Function pointer syntax can be hard to read. typedef is commonly used to simplify declarations:
typedef int (*CalcFunc)(int, int);
This defines CalcFunc as a type representing any function that takes two int parameters and returns an int.
Calling a Function via a Pointer #
int Max(int x, int y) {
return x > y ? x : y;
}
int main(void) {
int (*p)(int, int) = Max; // or &Max
int result = p(10, 20); // or (*p)(10, 20)
return 0;
}
Once assigned, a function pointer can be invoked just like a normal function.
🔁 What Is a Callback Function? #
A callback function is a function that is invoked indirectly through a function pointer.
In simple terms:
- Function A is passed as an argument to function B
- Function B later calls function A
- Function A is the callback
Core Roles #
- Library / Framework Code: Provides generic behavior
- Callback Function: Supplies application-specific logic
The caller does not need to know which function it is calling—only that the function matches the expected prototype.
Why Callbacks Matter #
Callbacks enable loose coupling and extensibility, and are heavily used in:
- Event handling (e.g., GUI or interrupt-driven systems)
- Algorithms (e.g., sorting and searching)
- State machines (e.g., hardware or protocol control)
- Operating systems and drivers
A classic example is qsort(), where the comparison logic is supplied by the user as a callback.
🛠 Practical Callback Examples #
Example A: Dynamic Math Operations #
A single handler can perform different operations by receiving different callback functions:
float add_sub_mul_div(float a, float b, float (*op_func)(float, float)) {
return op_func(a, b); // Callback invocation
}
// Usage
printf("Result: %f\n", add_sub_mul_div(10, 5, ADD));
printf("Result: %f\n", add_sub_mul_div(10, 5, SUB));
The handler remains unchanged while behavior varies dynamically.
Example B: Embedded State Machine (Professional Pattern) #
In embedded systems, callbacks are commonly used to implement state machines cleanly and safely.
typedef struct {
uint8_t mStatus;
uint8_t (*Function)(void);
} WorkStatus_TypeDef;
WorkStatus_TypeDef Status_Tab[] = {
{ OPEN_NETWORK, M26_PWRKEY_On },
{ INIT_PINS, M26_Work_Init },
{ CONFIG_AT, M26_NET_Config }
};
uint8_t Execute_State(uint8_t current_state) {
for (int i = 0; i < 3; i++) {
if (current_state == Status_Tab[i].mStatus) {
return Status_Tab[i].Function(); // Callback execution
}
}
return 0;
}
This pattern offers:
- Clear state-to-handler mapping
- Easy extensibility
- Strong separation of logic
It is widely used in firmware for modems, sensors, and real-time control systems.
🧾 Summary #
- Function pointers store addresses of executable code
- Callbacks allow code to invoke user-defined behavior indirectly
- They enable modular design, runtime flexibility, and clean abstractions
- Callbacks are fundamental in embedded systems, libraries, kernels, and event-driven software
Mastering callback functions is a key step toward writing professional-grade C code.