Book 16: Take Two and Call Me in the Morning

Previous | Back to Start

In the previous chapters, I frequently mentioned how convenient it is to accumulate libraries of useful functions. In the cases I demonstrated, this was easy: we were working with things like arrays, where you just handed it a bit of data and it did something with it. But what if your needs are more complex? What if your program has a reusable code module where some central part is freely configurable? In such a case, it would be nice to tell the operating system: “Do this, then call me back to take care of something else, then finish the job.”

As it just happens, the C programming language lets you do something like this. As you will probably remember, code is stored in the computer as simply another block of memory. Like any other block of memory, a function has an address. And since an address is just a regular old number, it can be passed to any other function as a parameter. This function can in turn use this address to actually call the function it was given.

In fact, this is what happens every time you call a function. The names of the functions that you write in your code are replaced by the compiler with the actual address of this function, and the brackets behind the function name that wrap around the parameters tell the compiler to call the code at that address. At first, this may sound like a needlessly complicated way of describing something very simple, but knowing that these two things are actually separate operations gives you the ability to do much cooler things: for example, you can put the address of a function into a variable, then write that variable before the brackets for a function call. This way you can call a function given to you by another function as a parameter.

Sadly, the syntax used in C to describe a function pointer is a little on the illegible side. But don’t be put off by that, it is a very useful and essentially easy to use feature, once you have memorized the slightly cryptic way of declaring a function pointer.

Since the best way is probably just to use it, let us do just that: the little program we will be writing is simply a variation on our good old calculator. Instead of comparing the operator that the user typed in, and then immediately performing the operation, we will create a function for each operation and store the address of the correct function for the current operator in a function pointer variable. Then later, we can simply ask the user for the parameters and hand them off to our function, without having to remember what the actual operation to do really is.

Now comes the ugly part: since every function has a different return type and a different number and different types of parameters, declaring a variable to hold a function’s address means you have to tell the compiler what return type and what parameters this function will accept. In our case, the function should take the two numbers the user entered as its parameters, so it should accept two integers. In addition, it should return the result of the calculation, which is also an integer. So, our function for adding two numbers would look like the following:

int DoAdd( int inFirstArg, int inSecondArg )
{
    return inFirstArg + inSecondArg;
}

The declaration for a variable that can hold a pointer to that function would look like this:

int     (*vOperationFunction)( int firstArg, int secondArg );

You can easily see that the return value is specified first and the parameters are specified between brackets at the end. However, it’s different from a regular variable declaration, in that the name of the variable (vOperationFunction) doesn’t follow all this type information, but rather was just placed where the function name would usually go. So in this case, we defined a variable named vOperationFunction that could hold a pointer to our different mathematical operation functions. Since a function, in theory, could also return a pointer to an int, you must put brackets around the asterisk that goes in front of the variable name to indicate it is a function pointer. If you forget these brackets, the compiler will think you are simply telling it about the existence of a function that returns a pointer to an int. Since you cannot change the address of a function, you would get weird error messages, telling you that the compiler cannot assign to the operation function.

Once you have declared this function pointer, you can easily assign the address of any function to it. For example, to put the address of our DoAdd function into our function pointer variable, we write:

vOperationFunction = DoAdd;

And to call this function, you simply write the name of the pointer variable where you would usually have the name of the function to call. Note that there is no need for the asterisk-operator to actually dereference the function pointer. Writing the brackets after the name automatically takes care of that.

Go ahead, try rewriting our calculator from the earlier chapters to use function pointers. Remember that you can use the %c format string to insert whatever operator the user entered into the printf() statement that shows the user the result of the calculation.

Now, when you look at your code, it may look a little silly to use function pointers to perform the various calculations, but keep in mind that this is intended as a simple exercise to get you started. Function pointers are a valid data type in C, just like any other one, and you can do all the things with them that you can do with any old int.

Think about what else you could do: If you were writing your own Terminal, instead of having the user type in operators and numbers, you could have them enter command names and parameter strings, and your functions could do much more complex things. You could have an array of structs, containing the operator or command name and the function pointer. What is now your main function could be turned into a fairly generic function to which you give such an array of function pointers and names, and it would pick the right function to call.

You would write that main loop that asks for user input exactly once, and use it in dozens of completely different applications, in their main functions, each with a different array of commands… The possibilities are limitless, and I’m sure once you’ve started writing your own one or two applications, you’ll quickly find lots of new uses for function pointers.

That’s pretty much what I wanted to say about function pointers, but before I leave you to your own coding, here’s an example of how I would write the function-pointer-using calculator:

#include <stdio.h>
#include <stdbool.h>

int GetArgument( bool isLeft )    // Nothing changed here.
{
    int    vNum;

    if( isLeft )
        printf( "Enter left argument: " );
    else
        printf( "Enter right argument: " );
    scanf( "%d", &vNum );
    fpurge( stdin );

    return vNum;
}

// Our operator functions:
int DoAdd( int inFirstArg, int inSecondArg )
{
    return inFirstArg + inSecondArg;
}

int DoSubtract( int inFirstArg, int inSecondArg )
{
    return inFirstArg - inSecondArg;
}

int DoMultiply( int inFirstArg, int inSecondArg )
{
    return inFirstArg * inSecondArg;
}

int DoDivide( int inFirstArg, int inSecondArg )
{
    if( inSecondArg == 0 )
    {
        printf("Flunked maths class? You can't divide by zero!\n");
        return 0;
    }
    return inFirstArg / inSecondArg;
}

int main()
{
    int     vFirstArg,
            vSecondArg,
			vResult;
    char    vOperation;
    bool    vFinished;
    int     (*vOperationFunction)(int firstArg, int secondArg);

    // Make sure our flag is initialized!
    vFinished = false;

    // Now loop until user doesn't want anymore:
    while( vFinished != true )
    {
        printf( "What operation do you want to do?\n" );
        scanf( "%c", &vOperation );
        fpurge( stdin );

        if( vOperation == '+' )
            vOperationFunction = DoAdd;
        else if( vOperation == '-' )
            vOperationFunction = DoSubtract;
        else if( vOperation == '*' )
            vOperationFunction = DoMultiply;
        else if( vOperation == '/' )
            vOperationFunction = DoDivide;
        else
            vFinished = true;

        if( !vFinished )
        {
            vFirstArg = GetArgument( true );
            vSecondArg = GetArgument( false );

            vResult = vOperationFunction( vFirstArg, vSecondArg );

            // Print "5 + 5 = 10" or whatever:
            printf("%d %c %d = %d\n", vFirstArg, vOperation, vSecondArg, vResult);
        }
    }

    printf( "Finished.\n" );

    return 0;
}

Previous | Back to Start

This entry was posted in C Tutorial. Bookmark the permalink.

One Response to Book 16: Take Two and Call Me in the Morning

  1. Stefan Jenkins says:

    Thanks for the tutorial! I recall being a little challenged by some of the getchar() challenge modifications…. and there was some stuff about the bit switching that I understood but haven’t had a chance to apply yet.

    I’ve studied a bit of C before, but this tutorial really helped me get back into the swing of things. It was well laid out and the examples were VERY helpful.

Leave a Reply

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


*