Tales of System Programming - 3

Function Pointers

Hello there, Hope you are doing well : )

A Function pointer is a pointer that holds the address of a function. This type of pointer is useful for controlling the execution sequence within an application by allowing alternate functions to be executed based on the application’s needs.

The ability of pointers to point to functions turns out to be an important and useful feature of C. This concept provides us with another way of executing functions in an order that may not be known at compile time. And of course, there are a few cons as well which we discuss a bit later.

Declaring Function Pointers

The syntax for declaring a pointer to a function can be confusing when you first see it. Let's start with a simple declaration. Here is the pointer to a function that is passed void and returns void:

void (*fptr)();

This declaration looks a lot like a function prototype. If we removed the first set of parentheses, it would appear to be a function prototype for the function fptr, which is passed void and returns a pointer to void. However, the parentheses make it a function pointer with a name of fptr. The asterisk indicates that it is a pointer. A few other examples of function pointer declarations are shown below:

int (*f1)(double);    // Passed a double and returns an int
void (*f2)(char *);  // Passed a pointer to char and returns void
double* (*f3)(int, int) // Passed two integers and returns a pointer // and returns a pointer to a double

We can easily be confused with functions that return a pointer with function pointers. For example:

int *f1() : Declares f1 as a function that returns a pointer to an integer

int (*f2)() : A function pointer that returns an integer

int* (*f3)() : The variable f3 is a function pointer that returns a pointer to an integer

Using a Function Pointer

Here is a simple example using a function pointer where a function is passed an integer and returns an integer. We also define a sum function that adds two integers and then returns the sum.

int (*fptr1)(int);

int sum(int a, int b){
    return a+b;
}

To use the function pointer to execute the sum function, we need to assign the sum function’s address to the function pointer, as shown below. As with array names, when we use the name of a function by itself, it returns the function’s address. We also declare two integers that we will pass to the function:

int a = 5;
int b = 10;
fptr1 = sum;
printf("Sum of %d and %d is: %d\n", a, b, fptr1(a,b));

We could have used the address-of operator with the function name but it is not necessary and is redundant.

Passing Function Pointers

Simply use a function pointer declaration as a parameter of a function. We will demonstrate passing a function pointer using add, subtract, and performComputation functions as declared below:

int add(int a, int b){
    return a+b;
}
int subtract(int a, int b){
    return a-b;
}

typedef int (*fptrOperation)(int, int);
int performComputation(fptrOperation operation, int n1, int n2){
    return operation(n1, n2);
}

// The following lines demonstrates these functions
printf("%d\n", performComputation(add, 5, 10));
printf("%d\n", performComputation(subtract, 25, 15));

As you've guessed by this time, The add and subtract function’s addresses were passed to the performComputation function. These addresses were then used to invoke the corresponding operation.

Returning Function Pointers

Returning a function pointer requires declaring the function’s return type as a function pointer. By using the above code, we can demonstrate how this can be done.

We will use the following selectOperation function to return a function pointer to an operation based on a character input. It will return a pointer to either the add function or the subtract function, depending on the opcode passed:

fptrOperation selectOperation(char opcode){
    switch(opcode) {
         case '+' : return add;
         case '-' : return subtract;
    }
}

Now, to tie these two functions we use the following evaluate function. The function is passed two integers and a character representing the operation to be performed. It passes the opcode to the selectOperation function, which returns a pointer to the function to execute. In the return statement, it executes this function and returns the result:

int evaluate(char opcode, int n1, int n2) {
    fptrOperation operation = selectOperation(opcode);
    return operation(n1, n2);
}

// This function demonstrated with the following printf statements
printf("%d\n", evaluate('+', 5, 10);
printf("%d\n", evaluate('-', 25, 15);

The output will be 15 and 10.

We can also dive into a bit more complex application of Function Pointers, the concept of Array of Function Pointers. We simply use the function pointer declaration as the array’s type. Here is an example:

typedef int (*operation)(int, int);
operation operations[128] = {NULL};

//OR, without using typedef
int (*operations[128])(int, int) = {NULL};

Concerns

One concern regarding the use of function pointers is a potentially slower-running program. The processor may not be able to use branch prediction in conjunction with pipelining.

Branch prediction is a technique whereby the processor will guess which multiple execution sequences will be executed. Pipelining is a hardware technology commonly used to improve processor performance and is achieved by overlapping instruction execution. In this scheme, the processor will start processing the branch it believes will be executed. If the processor successfully predicts the correct branch, then the instructions currently in the pipeline will not have to be discarded.

However, this slowdown may or may not be realized. These concerns are a price we need to pay for all the worth of flexibility we are getting by the use of Function Pointers.

Hope you enjoyed reading the concept of Function Pointers.

Keep Learning!