Function pointers

Function pointers are a commonly used feature of both C and C++, though perhaps to a lesser extent in C++ because some of the use cases are obviated by other language features. They seem to be the cause of some confusion, especially among beginners, so I thought it might be useful to go through a detailed introduction. If you are already comfortable with function pointers, I doubt you will learn much here, though I’d be interested in feedback on how to improve the content. Or perhaps have a look at member function pointers.

The following code is a simple example of using function pointers to get the ball rolling:

// Declare a function pointer **type**, FuncPtr, with
// signature int(*)(int).
using FuncPtr = int (*)(int);

// Define some simple functions.
int square(int x) { return x * x; }
int cube(int x)   { return x * x * x; }
int fourth(int x) { return x * x * x * x; }

int test(int x, int power)
{
    // Declare a function pointer **variable**, func_ptr.
    FuncPtr func_ptr;
    
    // Take the address of one of the functions.
    switch (power)
    {
        case 3:  func_ptr = cube;   break; 
        case 4:  func_ptr = fourth; break; 
        default: func_ptr = square;  
    }    

    // Call whichever function was selected.
    return func_ptr(x);
}

Aside: Although it may seem odd at first, functions have addresses. The compiled code is just a bunch of bytes, and must live somewhere in the software’s address space. Calling the function amounts to setting the processor’s program counter to this address. There is more to do in order to pass arguments, save the current processor state, and the like, but that’s the core idea.

Functions have types

To understand function pointers, I think it may be useful to consider function types a little. Every function has a function type associated with it which is derived from the return type and the argument types. For example, a function whose prototype is void foo(int x, double y) has a type of void(int,double) – you literally just remove the function name and argument names.

  • The function void bar(int alpha, double beta) has the same type as foo.
  • The member function void Class::member(int a, double b) has the same type.
  • The function int baz(int x, int y, int z) has a different type, which is int(int,int,int).

A function type is somewhat like a data type such as int, but you can’t do a lot with it. You can’t even declare variables of a function type. What would they even be? I guess they’d be whole functions. Although I have never seen this in practice, you can create an alias for a function type and use it to declare functions. Like this:

// Alias for a function type
using FuncType = void(int, double);

// Equivalent to void gamma(int, double); 
FuncType gamma; 

// Equivalent to void delta(int, double); 
FuncType delta;

struct Thing
{
    // Equivalent to void member(int, double); 
    FuncType member;
};

The alias can be used to declare functions but not to define them. So to implement Thing::member you need to write this:

void Thing::member(int, double)
{
}

This is quite an obscure corner of the language which I’m only including for background. I would definitely avoid doing anything directly with function types like the examples just given: you will just confuse everyone. Note that my description of function types is very informal compared to the C and C++ standards. You should check those for full details. But I think I’ve covered enough for our purposes.

Function pointers = pointers to function types

I said above that you can’t declare variables of function type. However, what you can do is declare variables whose type is pointer to function type: these are function pointers. In much the same way as data pointers hold the addresses of data objects, function pointers hold the addresses of functions. Function pointers are much more interesting and useful than function types.

Sadly, declaring function pointers is not pretty. You already know this, but to declare a variable of type pointer to int or pointer to double, you just do this:

// x is a variable of type pointer to int
int* x;    

// y is a variable of type pointer to double
double* y; 

Simple enough. To declare a variable of a pointer to function type such as void(int,double) you have to use some quite ugly and confusing syntax:

// fizz is a variable of type 
// pointer to void(int,double)
void (*fizz)(int, double);

// buzz is a variable of type 
// pointer to int(int,int,int)
int (*buzz)(int, int, int);

Both fizz and buzz are function pointers. The names of these variables are mixed into the signatures when declaring them. This is often a source of great confusion. I think this might be my candidate for the ugliest syntax in all of C and C++. We will shortly simplify these declarations…

Even though both fizz and buzz are function pointers, it is important to understand that they have completely distinct data types. They are as distinct as x and y above, an int* and a double*, respectively.

  • Function pointers are data types. They may be null, or may hold the address of a function which has a matching signature (i.e. a matching function type).
  • Function pointers can be used in all the same places as other data types: as local variables, as members of structs and classes, in arrays, in STL containers, allocated on the heap (I’ve never seen this), as non-type template parameters, and so on.
  • Function pointers can also be used in expressions with comparison and boolean operators but, unlike data pointers, arithmetic and bitwise operators make no sense and are illegal in C++.
  • A function pointer type itself can also be used as a template parameter.

So that has hopefully cleared up what function pointers are. The basic syntax for declaring them is just horrible. So…

Aliases for function pointer types

This section looks at how to use aliases to make function pointer declarations look just like other variable declarations. There a few common ways to alias a function pointer type.

Typedef

The first is inherited from C and is something you should become familiar with even though I don’t recommend using it in your own code. You will see it a lot in older C++ code, and it is the only method available in C. This method creates an alias with typedef. The syntax is exactly the same as declaring a variable above, but with the keyword typedef in front:

// Old style function pointer **type** - avoid.
typedef void (*OldFuncPtr)(int, double);

// ptr is a function pointer.
OldFuncPtr ptr; 

// vec is a vector of function pointers.
std::vector<OldFuncPtr> vec;

// Thing::m_ptr is a function pointer.
// (*NOT* a pointer to a member function, but
// a plain function pointer which is a data 
// member.)
class Thing
{
    OldFuncPtr m_ptr;
};

// func is a non-type template parameter for 
// class Something.
template <OldFuncPtr func>
class Something {};

The code includes a few use cases for OldFuncPtr to demonstrate (I hope) that using the alias is simpler than using the raw function pointer syntax. We could, for example, have defined vec as follows, which is equivalent. You will sometimes see code like this, but I know which I prefer:

// vec is a vector of function pointers.
std::vector<void(*)(int, double)> vec;

Type alias

A typedef gives a more user friendly name to a function pointer type, but still uses the old C syntax when creating the alias. C++11 introduced a better type alias with the using keyword:

// New style function pointer type - preferred.
using NewFuncPtr = void (*)(int, double); 

This pulls the alias name of the function pointer type out from the middle of the signature and is, I think, a lot more readable. The code basically just says “this simple name is equivalent to this ugly thing”. NewFuncPtr is identical to OldFuncPtr and they can be used interchangeably. The parentheses around the star are required because of operator precedence – to distinguish our function pointer from a function type which returns void* in this example. I generally just read “(*)” as “pointer to function”.

Something to note is that type aliases can be templates. Like this:

template <typename T>
using FuncPtrT = void (*)(T, T);

// fi is a function pointer of type
// void (*)(int, int).
FuncPtrT<int> fi; 

// fd is a function pointer of type
// void (*)(double, double).
FuncPtrT<double> fd; 

This can be very useful sometimes. For completeness, if we wish, we can also alias this template for particular template parameters:

using IntFuncPtr = FuncPtrT<int>;

// fi2 is a function pointer of type
// void (*)(int, int).
IntFuncPtr fi2; 

Trailing return type

C++ introduced a variation on how to declare a function. The return type can come at the end instead of at the beginning. This is useful/essential when the return type is not known before the argument types are known to the compiler, which can happen in some templates. The trailing syntax looks like this:

// New style function pointer type - trailing return.
using TrailingFuncPtr = auto (*)(int, double) -> void; 

TrailingFuncPtr is identical to OldFuncPtr and NewFuncPtr, and they can be used interchangeably. auto is required at the start as a stand-in for the return type, and the actual return type comes at end after an arrow ->.

Some people now use the trailing syntax everywhere in their code for “consistency”. Personally, I don’t recommend this. I’m all for modernising code, but this seems to be gratuitously throwing away forty years of familiarity with billions of lines of code for zero gain. Change for change’s sake is not a good thing.

On the other hand, the trailing syntax does have a nice feature in this particular context. You can read the function pointer meaning directly from left to right: “TrailingFuncPtr is a pointer to a function which takes two arguments with types int and double, respectively, and returns void“. That’s much simpler than the C syntax, which bounces from going left to right to left again.

Using a function pointer alias

I have never seen this used anywhere, but we could theoretically declare an alias for a function type rather than a pointer to function type, and then declare pointers of that type. Like this:

// Alias for a function type
using FuncType = void(int, double);

// This is a pointer to functions with type
// void(int,double).
FuncType* func_ptr;

// Alias for a function pointer type
using FuncTypePtr = FuncType*;

// This is a pointer to functions with type
// void(int,double).
FuncTypePtr func_ptr2;

In this example, func_ptr and func_ptr2 have exactly the same data type. Given its apparent rarity, I’d probably avoid aliasing function types directly.

Using a function pointer variable

So far we have covered what function pointers are and how to alias them with simple names to hide the rather unpleasant syntax, but we haven’t actually used them for any function pointing. This is very simple: you just assign the function pointer with (the name of) a function, or its address, or the value of another function pointer, and then you use the function pointer to call the function of whose address it currently holds:

void cube(int x)
{
    std::cout << x << " cubed is " << (x*x*x);
}

void square(int x)
{
    std::cout << x << " squared is " << (x*x);
}

// Create an alias for the function pointer.
using FuncPtr = void (*)(int);
using DiffPtr = void (*)(int, int);

int main()
{
    FuncPtr ptr = &cube;  
    ptr(3); // Calls cube(3) 

    ptr = nullptr;
    ptr(3); // Nothing to call - crash

    ptr = square; // & optional 
    ptr(5); // Calls square(5)

    FuncPtr ptr2 = ptr; 
    ptr2(6); // Calls square(6)

    DiffPtr diff; // Uninitialised
    diff = cube;  // Doesn't compile
    diff = &cube; // Doesn't compile
    diff = ptr;   // Doesn't compile
}

Key things to note from the code in main():

  • Function pointers can be null or uninitialised.
  • Function pointers can be assigned and re-assigned
    • They can be directly assigned with the name of a function. This is analogous to assigning an int variable with an immediate value: “int x; … x = 42;”.
      • One of the curiosities of C (which C++ inherited) is that you can assign the function name directly, or take the address of the name with &. These are equivalent, and the compiler takes the address in both cases. I guess a function has no value that can be returned, so it makes sense to return its address instead.
    • They can be assigned to/from other function pointer variables. This is analogous to assigning an int variable with the value of another int variable: “int x, y; … x = y;”.
      • Note that using & on another function pointer will not work: this returns a pointer to a pointer to a function, which makes sense. Fear not – your code will not compile if you do this.
    • Function pointers cannot be assigned with a function name (or function pointer) which does not have the same signature. This is analogous to not being able to assign a variable of type Foo to a variable of type Bar (operator overloading notwithstanding).
      • This makes sense if you think about it. If the function at some address expects two integers arguments to be passed to it then it will almost certainly break if you call it through a function pointer which passes a double or something. Arguments are placed on the stack before jumping to the function’s address: the function knows how to unpack its arguments from the stack. If something else is there instead, there will be trouble.
  • Function pointers are used with exactly the same function call syntax as a regular function with the signature they represent.
    • You can think of () as an operator which, when applied to a function pointer, calls the function which is being pointed at.
    • In fact, it turns out that () actually is an operator, and one that classes can overload with custom implementations. This is for another day.

Function pointers v data pointers

When you think about it, a function pointer seems a lot like a data pointer.

  • Data resides in memory at some particular location in the software’s address space, and can be accessed by de-referencing a data pointer which holds that location (that is, the address of the data). The address of the data is the value of the data pointer.
  • By the same token, executable code resides in memory at some particular location in the software’s address space, and can be executed by placing that location (that is, the address of the function) into the processor’s program counter. The address of the function is the value of the function pointer.

It’s a nice analogy, but it’s a mirage. Data pointers and function pointers are not really the same thing, and they are not at all interchangeable. They are not even necessarily the same size (on Cortex-M, both are four bytes). Depending on the architecture, functions may not even reside in the same address space as data. That is, you could theoretically have both data with address 0x00020000 and a function with address 0x00020000, but with no overlap because they are in independent address spaces (for example, Harvard architecture devices).

So if you ever find yourself casting a data pointer into a function pointer, or vice versa: your code is probably broken. It might work if you call the function, depending on the architecture and the implementation, and/or it might start a game of Global Thermonuclear War. I’m no language lawyer, but it is also questionable whether casting between function pointers with different signatures is well-defined. Remember: casting is lying to the compiler. Or, if you prefer, it is saying to the compiler “Look away! I totally know what I’m doing”. Which is very often the same thing in my experience.

But still, when it comes right down to it, they are both just data types whose variables hold addresses, which are just numbers. Function pointers are not mysterious black magic, and that’s good to know.

Why are function pointers useful?

We’ve covered what function pointers are and how to use them a little bit, so now let’s finally look at some ways they are used in practice. Function pointers offer a simple indirection mechanism that can be used in many different ways to call a function whose identity is not necessarily known at compile time. We already saw an example of this in the function test() at the beginning of this article. The following sections demonstrate a few more examples.

Lookup table

Lookup tables are very common. One example might be in the implementation of a finite state machine. If all the action functions have the same signature, they could be stored in a table which is indexed by the current state and the current event. Or we could have something as simple as this:

// Declare a function pointer type.
using Func = int (*)(int);

// Define some simple functions.
int square(int x) { return x * x; }
int cube(int x)   { return x * x * x; }
int fourth(int x) { return x * x * x * x; }

// A lookup table of function pointers.
Func g_table[] = { square, cube, fourth };

int test(int x, int index)
{
    assert(index < 3);
    return g_table[index](x);
}

Virtual functions are typically implemented internally using a table of function pointers: the vtable. The compiler does not know which implementation of a given virtual function you will call at run time, but it does know the index of that function in the vtable. So calling a virtual function involves a simple lookup in a table with a compile time constant index to find a function pointer. A lot of people seem to think virtual functions are very expensive. This is not directly true, but they can have an impact on cache hits, and cannot easily be optimised away. But if you actually need run time polymorphism, it could hardly be more efficient. [Note that I am glossing over the fact that virtual functions are member functions, and that pointers to member functions are not at all the same creatures as pointers to free functions. The compiler knows its business.]

Interface structure

I’ve often seen structures containing a set of function pointers which collectively provide an interface to some behaviour. This can be used in C to sort of modularise code. The client of the interface specifies what the functions do, but has no idea how they are implemented. An instance of the structure is populated somewhere during initialisation, and may even change during program execution. You almost never see code like this in C++ because this is a perfect use case for virtual functions in abstract base classes.

// Create instance of structure and populate somewhere.
struct Funcs
{
    // Direct signature usage to avoid three typedefs - lazy!
    void (*fiddle)(int);
    void (*faddle)(int, int);
    void (*muddle)(int, int, int);
    // ...
};

// Funcs likely passed as a pointer in C.
void test(int x, const Funcs& funcs)
{
    // Assume function pointers all non-null.
    funcs.fiddle(x);
    funcs.faddle(x, x);
    funcs.muddle(x, x, x);
}

Speaking of virtual functions, although they are always described as having function pointers in a “table”, the functions do not all have the same signature. I wonder if the reality is in principle more like the interface structure in the example here. But it really makes no difference since this is just a detail of the implementation. Function pointers are just numbers, and the implementation knows which function pointer type corresponds to each index in the table. I think it is safe to assume that the compiler knows how to make virtual functions work properly.

Callback functions

One of the most common idioms that uses function pointers is callback functions, so I’ll cover them in some detail.

Imagine that you are using a library which needs to notify your code when certain things happen. Perhaps it internally implements an interrupt handler for some GPIO input and your application needs to know when an interrupt occurs. You cannot easily replace the interrupt service routine with your own, but the library lets you set a callback function. It might be implemented like this:

// Library code 
// ------------
// In the public header file
using IntHandler = void (*)(bool state); 
void set_interrupt_handler(IntHandler handler);

// In the private implementation file
IntHandler g_handler = nullptr; 
void set_interrupt_handler(IntHandler handler)
{
    g_handler = handler;
}

extern "C" void GPIO_IRQ_Handler()
{
    // Clear interrupt flags
    // ...

    // Read state of the pin
    bool state;
    // ...
    
    // Call the callback function
    if (g_handler)
    {
        g_handler(state);
    }
}

The library declares a function pointer type/alias and a function you can call in order to pass it the address of a matching function, which it will store in g_handler. You can now use this feature in your own code something like this:

// Your application code
// ---------------------
// This is the callback function. The 
// signature matches IntHandler.
void on_interrupt(bool state)
{
    // Do something here
}

int main()
{
    // Configure the pin using the library.
    // Pass the callback function to the library.
    // ...
    set_interrupt_handler(on_interrupt); 

    // ...    
}

And now on_interrupt() is called by the library whenever an interrupt occurs. From the application’s point of view, it is trivial to implement. From the library’s point of view, it’s a bit more effort, but not much.

Using function pointers like this is a staple of C interfaces. As with the other examples, callbacks are used all over the place.

Passing context information to callback functions

It is very common in such C interfaces to pass a void* value along with the function pointer. The value of the void* is meaningless to the library but can be used to give your own code some context when the callback is actually called. You might, for example, pass the address of a struct which contains some state which is useful in your application. This idiom is so common in C code that you are certain to encounter it. So I’ll include the full code again. I’ll use C-style declarations this time:

// Library code 
// ------------
// In the public header file - argument names optional but helpful
typedef void (*IntHandler)(bool state, void* context); 
void set_interrupt_handler(IntHandler handler, void* context);

// Or, if the C dev hates other people they might not bother 
// with the typedef at all. Don't do this in your code. I mean, 
// seriously, writing code like this is the very definition of 
// misanthropic:
void set_interrupt_handler(void (*handler)(bool, void*), void* context);

// In the private implementation file
// These two values might be placed in a struct to associate them.
IntHandler g_handler = nullptr; 
void*      g_context = nullptr;

// Or, if the C dev hates other people ...
void (*g_handler)(bool state, void* context); 

void set_interrupt_handler(IntHandler handler, void* context)
{
    g_handler = handler;
    g_context = context;
}

extern "C" void GPIO_IRQ_Handler()
{
    bool state;
    if (g_handler)
    {
        // Call the callback function, passing context.   
        g_handler(state, g_context);
    }
}

And now for your application code:

// Your application code
// ---------------------
struct SomeInfo { /* ... */ };

void on_interrupt(bool state, void* context)
{
    // Do something here with the context
    SomeInfo* info = static_cast<SomeInfo*>(context);
    // ...
}

SomeInfo g_interrupt_info; 

int main()
{
    // Configure the pin using the library.
    // Pass the callback function and context to the library.
    // ...
    set_interrupt_handler(on_interrupt, &g_interrupt_info);     

    // ...    
}

This is not much harder to use than the version without context, but gives your code more scope for disambiguation if the same callback is used more than once.

Conclusion

I hope I have managed to shed at least some light on the nature and uses of function pointers. For me, the notion that function signatures themselves are just simple data types was not made clear early on, and I think this would have been helpful to understand.

Although they are useful and interesting, and kind of essential reading in the embedded domain, function pointers are really a very C way of doing things. This is not entirely the case, but C++ offers other abstractions which are often simpler to use. Virtual functions are a good example: one of the main uses for function pointers in C is to create run-time polymorphism which is almost indistinguishable from virtual functions. The built-in C++ version is efficient, and offers the compiler better opportunities for optimisation. Callback functions are still very important, especially if you have to integrate your application with C, which is usually the case in the embedded world.

The next article will move on to the generalisation of function pointers in C++: member function pointers. These are way more fun!

Published by unicyclebloke

Somewhat loud, red and obnoxious.

One thought on “Function pointers

Leave a comment