Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
C Callback Functions Explained
Pass a function’s address to another function and let the receiver call it back. Learn callback functions in C with an ADC conversion example, typedef, and qsort.
A callback function in C is a function you hand to another function so that it can be called later, at the right moment. It is the everyday use of function pointers, and it lets one piece of code stay generic while the caller decides the behaviour. This lecture of our free C programming course builds callbacks step by step with original examples, the kind you will meet in embedded C programming for sensors, timers and communication drivers.
What You Will Learn
- How callback functions in C receive a function pointer as a parameter
- The calling and called roles in a callback, with a clear picture of the flow
- How to simplify long declarations with
typedef - How the standard library function
qsortuses a comparison callback - Safe habits: NULL checks, matching signatures and const correctness
- Where callbacks appear in C programming for embedded systems
Prerequisites
Finish the previous lecture on calling functions through pointers first. You should also be comfortable with arrays, passing arrays to functions and the size_t type.
The Idea Behind Callback Functions in C
Imagine a routine that walks through an array of sensor readings and needs to process each value. The walking part never changes. The processing part does: today you convert raw counts to millivolts, tomorrow you clamp values, next week you scale them for a display. Rather than writing three near-identical loops, you write the loop once and let the caller supply the processing step as a parameter.
A function parameter that receives the address of a function is declared with the same syntax as a function pointer variable. Inside the receiving function you call it exactly as you did in the last lecture. The receiver does not know what the callback does, only its signature. The caller does not know how the loop works, only that it will call back once per element.
Declaring a Callback Parameter
Written out in full, a function that accepts a callback looks like this:
void transform_all(int *buf, size_t n, int (*fn)(int));
The last parameter says: fn is a pointer to a function that takes an int and returns an int. Reading these declarations gets tiring, so give the pointer type a name with typedef:
typedef int (*xform_fn)(int);
void transform_all(int *buf, size_t n, xform_fn fn);
The typedef gives the parameter list back its readability. Embedded code bases use this style almost universally, for example a typedef void (*irq_handler_t)(void) for interrupt service routines.
Complete Callback Example in C
Create ep_callback.c. A 12-bit ADC returns values from 0 to 4095 for a 3.3 V reference. The program converts the raw readings to millivolts, caps them at 2000, and finally sorts them in descending order with qsort, which takes a comparison callback of its own.
#include <stdio.h>
#include <stdlib.h>
typedef int (*xform_fn)(int);
static int to_millivolt(int raw)
{
return raw * 3300 / 4095;
}
static int cap_at_2000(int mv)
{
return (mv > 2000) ? 2000 : mv;
}
static void transform_all(int *buf, size_t n, xform_fn fn)
{
if (buf == NULL || fn == NULL)
return;
for (size_t i = 0; i < n; i++)
buf[i] = fn(buf[i]);
}
static void show(const char *tag, const int *buf, size_t n)
{
printf("%-8s:", tag);
for (size_t i = 0; i < n; i++)
printf(" %4d", buf[i]);
printf("\n");
}
static int by_value_desc(const void *a, const void *b)
{
int x = *(const int *)a;
int y = *(const int *)b;
return (y > x) - (y < x);
}
int main(void)
{
int samples[5] = { 0, 1024, 2048, 3071, 4095 };
size_t n = sizeof samples / sizeof samples[0];
show("raw", samples, n);
transform_all(samples, n, to_millivolt);
show("mV", samples, n);
transform_all(samples, n, cap_at_2000);
show("capped", samples, n);
qsort(samples, n, sizeof samples[0], by_value_desc);
show("sorted", samples, n);
return 0;
}
Build and Run
gcc -Wall -Wextra -std=c11 -o ep_callback ep_callback.c
./ep_callback
Expected output:
raw : 0 1024 2048 3071 4095
mV : 0 825 1650 2474 3300
capped : 0 825 1650 2000 2000
sorted : 2000 2000 1650 825 0
Reading the Output
- The first line is the raw ADC counts as sampled.
- After
transform_all(samples, n, to_millivolt)each value becameraw * 3300 / 4095. Full scale 4095 gives 3300, and mid-scale 2048 gives 1650 because integer division truncates. - The second call reused the same loop with
cap_at_2000, which limited the two highest values. The loop code did not change at all. qsortthen ordered the array from largest to smallest using our comparison function.
How qsort Uses a Callback Function
The standard library cannot know how to compare your data, so qsort accepts a pointer to a comparison function with this fixed shape: it receives two const void * pointers to elements and returns a negative, zero or positive int. Our by_value_desc casts each pointer back to const int *, reads the values, and returns (y > x) - (y < x). That expression yields -1, 0 or 1 without risking the integer overflow that a plain y - x can cause for extreme values.
| Item | Who writes it | Who calls it |
|---|---|---|
transform_all | Library or driver author | Application code |
to_millivolt (callback) | Application author | transform_all |
qsort | C standard library | Application code |
by_value_desc (callback) | Application author | qsort |
Tip: keep callback signatures small and honest
A callback with a clear single job is easy to test on its own. If a callback needs extra data, such as a scale factor, the usual C pattern is a second void *context parameter passed through by the caller. We keep today’s examples simple, but you will see that pattern in real driver APIs.
Callback Functions in C for Embedded Programming
- Timers: register a function to run when a hardware timer expires.
- UART or SPI drivers: register a function to be called when a receive buffer is full.
- Button handling: call a registered function when a debounced press is detected.
- RTOS tasks: a task is created with a pointer to the function it will run.
In every case the driver stays reusable because the application supplies the behaviour.
Common Mistakes with Callback Functions in C
| Mistake | What happens | Fix |
|---|---|---|
Passing to_millivolt(5) instead of to_millivolt | The function is called immediately and its int result is passed; type error | Pass the bare name, with no parentheses |
| Callback signature differs from the parameter type | Incompatible pointer warning or error | Match return and parameter types exactly |
| Not checking for NULL | Crash when the callback is optional | Test fn == NULL first, as transform_all does |
| Doing long work inside an interrupt callback | Missed interrupts, jitter | Set a flag or queue a message, process it in the main loop |
| Comparator that subtracts values | Overflow gives wrong sort order | Return the result of two comparisons, as shown above |
Best Practices for Callback Functions in C
- Use a typedef for each callback type and name it for its purpose.
- Document what the receiver guarantees: when it calls, from which context, and how often.
- Mark parameters
constwhen the callback must not modify them. - Check the callback pointer for NULL before use if registering is optional.
Performance and Security Notes
Each callback invocation is an indirect call, which prevents inlining. For a loop over five samples nobody cares, but for a per-byte callback at high data rates, consider a version of the loop specialised for the hot case. On the security side, only accept callback pointers from code you trust, and never store them in buffers that user-controlled data can overflow.
Summary and Key Takeaways
- Callback functions in C are functions whose address is passed to another function that calls them later.
- The parameter is declared with function pointer syntax; a typedef makes it readable.
- Pass the function name without parentheses; the receiver calls it with
fn(arg). qsortis the standard example: it needs your comparison function to order your data.- Check for NULL and keep signatures exact.
Conclusion
Callback functions in C separate what to do from when and how often to do it. One generic loop can serve many behaviours, and one driver can serve many applications. That is why they dominate C code that talks to hardware. Next, we will store several function pointers in an array, so a program can pick behaviour by index.
Frequently Asked Questions
What is a callback function in C?
It is a function passed by address to another function, which calls it at a suitable moment. C implements this with function pointers.
How do I pass a function as an argument in C?
Declare the parameter as a function pointer, and pass the function’s name with no parentheses. Inside the receiver, call it like fn(value).
Why use typedef for callback types?
It names the type once, so prototypes read like void run(xform_fn fn) instead of repeating the full pointer syntax.
How does qsort know how to compare my data?
You supply a comparison callback that receives two element pointers and returns negative, zero or positive. qsort calls it repeatedly while sorting.
Can a callback take extra data?
Yes. A common design adds a void *context parameter that the caller passes through, so the callback can reach its state without globals.
Are callbacks used in embedded systems?
Everywhere: timer expiry, receive-complete events, button presses, RTOS task entry points and interrupt handlers.
Learn Embedded C Programming for Free
This free C tutorial continues with arrays of function pointers and dispatch tables.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments