C for Yourself - part 60

Calling functions by pointer - Steve Mumford looks at the details

This month I'll be investigating pointers to functions, and explaining some of the reasons why they can be particularly useful, especially when developing the shell of an application. Obviously, whenever pointers are mentioned we know we'll be referring to blocks of memory. At this stage we should be comfortable with the notion that if, for example, an array a[20] has been created, the value of a on its own gives the starting position of the array in memory.

We've been using that feature to pass the addresses of memory buffers to the OS when making SWI calls, but given this start address, it's also possible to access array elements by memory offset.

Functions can also be thought of as blocks of memory and, although their contents aren't editable, it's still possible to call the function concerned if you know its starting location. By using a pointer to store the starting address of a function, we can call that function without needing to know its name. In this way, we can create a link to a procedure that can be changed as many times as we want during the execution of the application.

In order to determine the start address of a function, we use its name without any brackets or arguments, in exactly the same way that we find the address of an array block. However, we still need to create a pointer variable to hold this information, and that's not quite so straightforward. The syntax might look a little confusing, but it does make sense once you understand where the components come from.

In fact, it's very similar to the format we use when we're prototyping functions at the beginning of a program - here's an example. For the function shown below:

int func(int x, int y)
{
  return x + y;
}

the function pointer declaration would look like this:

int (*funcptr)(int, int);

The brackets around *funcptr are required to stop the C compiler from working itself into a frenzy. Other than that, you can see that it is indeed very similar to the actual function prototype, shown below:

int func(int, int);

Once you've declared the function pointer in this way, you're free to use it as you see fit. Here's how we'd call func using the above function pointer:

funcptr = func;
result = (*funcptr)(1, 2);

It's even possible to make use of these pointers as formal parameters within a function definition, meaning you can write a procedure that accepts the name of a function along with its other arguments.

So why are function pointers useful? You could be forgiven for thinking that they add an unnecessary complication to the whole process of writing a program. It's true that pointers always add an extra layer of complexity, but very often the controlled inclusion of pointers can give you more freedom in just how you structure your code.

Up until now, functions have always been static objects - we can decide whether or not to call them by using standard C decision statements, but that's about the limit. By accessing our functions with pointers, we can start using them in many different ways - for instance, you can bring shades of C++ programming to your code by associating particular functions with data objects.

In our case we have a particular problem to solve - we've started writing a library of routines designed to make the programmer's life a little bit easier. However, these routines can't be included automatically, and it's up to the programmer to build them into his or her code as appropriate, in this situation, the programmer still ends up doing a lot of work from scratch, and it would be nice if this workload could be minimised.

One method might be to include a pre-written core program containing all of the basic features of the WIMP, which coders could then add to and modify. Using existing techniques, there has been no easy way of adding functionality to an older application, short of modifying the existing source code.

Now that we can use pointers to functions, the situation has changed somewhat. We can use these pointers as placeholders, referring to a generic function that performs a simple version of the task until the programmer replaces it with a more complete routine. For instance, the application kernel could contain basic window redrawing routines that capture the appropriate WIMP messages and deal with the moving, opening and closing of a window.

These would suffice until more intricate graphics were required, and at this stage a new redrawing function would be registered to take the place of the existing one. Without pointers, this would have required a change in the source code; with pointers it could be done on the fly. There are quite a few more applications for function pointers; I hope you find them useful. See you next time.


Source: Acorn User - 208 - June 1999
Publication: Acorn User
Contributor: Steve Mumford