What are C Callback Functions Explained- Online Embedded C Programming Classes

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Free C Programming Course · Pointers · Lecture 56

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 qsort uses 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.

Callback flow
1. main calls → transform_all(buf, n, to_millivolt)
2. transform_all loops and calls fn(buf[i]) → to_millivolt
3. to_millivolt returns value → stored back in buf[i]
4. transform_all finishes → returns to → main

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

  1. The first line is the raw ADC counts as sampled.
  2. After transform_all(samples, n, to_millivolt) each value became raw * 3300 / 4095. Full scale 4095 gives 3300, and mid-scale 2048 gives 1650 because integer division truncates.
  3. 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.
  4. qsort then 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.

ItemWho writes itWho calls it
transform_allLibrary or driver authorApplication code
to_millivolt (callback)Application authortransform_all
qsortC standard libraryApplication code
by_value_desc (callback)Application authorqsort

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

MistakeWhat happensFix
Passing to_millivolt(5) instead of to_millivoltThe function is called immediately and its int result is passed; type errorPass the bare name, with no parentheses
Callback signature differs from the parameter typeIncompatible pointer warning or errorMatch return and parameter types exactly
Not checking for NULLCrash when the callback is optionalTest fn == NULL first, as transform_all does
Doing long work inside an interrupt callbackMissed interrupts, jitterSet a flag or queue a message, process it in the main loop
Comparator that subtracts valuesOverflow gives wrong sort orderReturn 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 const when 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).
  • qsort is 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

Leave a Reply

Your email address will not be published. Required fields are marked *