C Function Pointers

Building a connected device?

Book a Free 30-min Call

Overview

C is a powerful language, and one thing that makes it so powerful is pointers. Pointers give you pretty much unlimited access and flexibility when writing programs. One exceptionally good application of pointers (specifically function pointers) is callbacks.

The general idea of callbacks can be quite confusing at first. So, let’s break this down.

Basic Concept

First and foremost, don’t get intimidated by the terminology. A callback is just a function, like any other normal function in C.

Here’s a completely normal function that turns on an LED:

void led_on(uint8_t pin)
{
   nrfx_gpiote_out_set(pin);
}

Here’s another that turns off an LED:

void led_off(uint8_t pin)
{
   nrfx_gpiote_out_clear(pin);
}

These completely normal functions can also be callbacks. Consider a simple example. Let’s say you have a function that reads a temperature sensor. Whenever the sensor reads a temperature greater than room temperature (22 degrees Celsius), something should happen with the LED. One traditional solution:

#define STANDARD_ROOM_TEMP 22

void generate_alert(void)
{
   int8_t room_temp = read_temperature();

   if(room_temp > STANDARD_ROOM_TEMP)
   {
      led_on();
   }
}

Fairly straightforward. But what if we wanted to turn off the LED, or blink it for a few seconds? What if we wanted to do something completely different?

Defining a Function Pointer

Let’s look at a more flexible solution using callbacks. To begin, consider the two action functions:

void led_on(uint8_t pin);
void led_off(uint8_t pin);

They both take a parameter of type uint8_t and return nothing. An appropriate function pointer declaration:

typedef void (*led_action)(uint8_t pin);

This is called a “function pointer” because it points to a function, not data. Read from the middle: the pointer points to some unknown, yet to be defined function that takes one parameter and returns nothing. The key is understanding that we are pointing to some function; we just don’t know what it is yet.

A good rule of thumb when declaring function pointers: use a name that is generic and encapsulates a wide range of expected actions in the given context. The name led_action gives a pretty good idea of the kind of functions it points to.

The typedef simply makes the code easier to read by declaring led_action as a type.

Using a Function Pointer

Now we can redefine our generate_alert function:

#define STANDARD_ROOM_TEMP 22

void generate_alert(led_action action)
{
   ASSERT(action != NULL);

   int8_t room_temp = read_temperature();

   if(room_temp > STANDARD_ROOM_TEMP)
   {
      action();
   }
}

You are basically saying: if the temperature is above 22, do something. What you actually do comes later.

The ASSERT line is good programming style. You are ensuring that the unknown function actually exists, otherwise you are in for a world of pain (undefined behaviour). Always check that the target function is defined before calling it.

If we wanted the LED to turn on when the temperature went past 22 degrees:

generate_alert(led_on);

If we wanted the LED off:

generate_alert(led_off);

We have made the generate_alert function more flexible and generic by passing a different action to it, depending on the requirement. This is what makes a normal function a callback: by passing it as an argument to another function.

Without typedef, the generate_alert function prototype would look like this:

void generate_alert(void (*led_action)(uint8_t));

With typedef, we get a much nicer-to-read prototype:

void generate_alert(led_action action);

Conclusion

Callbacks provide abstraction, flexibility, and decoupling. By using this format, we did not explicitly bind the generate_alert function to one specific LED action. When using function pointers and callbacks, remember:

  • Use function pointer names that cover the range of actions you will potentially perform; makes the code easier to read.
  • Use assertions to verify that the intended target function exists. Always use protection.
  • Use typedef to declare function pointers; makes your code much easier to read.

Tags

Best PracticesProgramming
TA

Timothy Adu

Senior embedded consultant based in Copenhagen, working with startups and product teams on connected hardware.

About Timothy →