Function Pointers
Function Pointers
A function pointer is a pointer that holds the address of a function. The ability of pointers to point to functions turns out to be an important and useful feature of C. This provides us with another way of executing functions in an order that may not be known at compile time and without using conditional statements.
Declaring Function Pointers
Let’s start with a simple declaration. Below, we declare a pointer to a function that is passed void and returns void:
void (*foo)();
When function pointers are used, the programmer must be careful to ensure it is used properly because C does not check to see whether the correct parameters are passed.
Other examples of function pointer declarations:
int (*f1)(double); // Passed 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 to a double
Using a Function Pointer
int (*fptr1)(int);
int square(int num) {
return num * num;
}
To use the function pointer to execute the square function, we need to assign the square function’s address to the function pointer.
int n = 5;
fprt1 = square;
printf("%d squared is %d\n", n, fptr1(n));
Output:
5 squared is 25
As with array names, when we use the name of a function by itself, it returns the function’s address.
Typedef of Function Pointers
It is convenient to declare a type definition for function pointers.
typedef int (*funcptr)(int);
funcptr fptr2;
fptr2 = square;
printf("%d squared is %d\n", n, fptr2(n));
The type definition looks a little bit strange. Normally, the type definition’s name is the declaration’s last element.
Passing Function Pointers
Passing a function pointer is easy enough to do. Simply use a function pointer declaration as a parameter of a function. We will demonstrate passing a function pointer using add, sub, and compute functions as declared below:
int add(int num1, int num2) {
return num1 + num2;
}
int sub(int num1, int num2) {
return num1 - num2;
}
typedef int (*fptrOperation)(int, int);
int compute(fptrOperation operation, int num1, int num2) {
return operation(num1, num2);
}
printf("%d\n", compute(add, 6, 5));
printf("%d\n", compute(sub, 6, 5));
Output:
11
1
The add and sub function’s addresses were passed to the compute function. These addresses were then used to invoke the corresponding operation. This example also shows how code can be made more flexible through the use of function pointers.
Returning Function Pointers
Returning a function pointer requires declaring the function’s return type as a function pointer.
We will use the following select function to return a function pointer to an operation based in a character input. It will return a pointer to either the add function or the sub function, depending on the opcode passed:
fptrOperation select(char opcode) {
switch(opcode) {
case '+': return add;
case '-': return sub;
}
}
int evaluate(char opcode, int num1, int num2) {
fptrOperation operation = select(opcode);
return operation(num1, num2);
}
printf("%d\n", evaluate('+', 6, 5));
printf("%d\n", evaluate('-', 6, 5));
Output:
11
1
The evaluate function ties these functions together. The function is passed two integers and a character representing the operation to be performed. It passes the opcode to the select function, which returns a pointer to the function to execute. In the return statement, it executes this function and returns the result.
Array of Function Pointers
Arrays of function pointers can be used to select the function to evaluate on the basis of some criteria. Declaring such an array is straightforward. We simply use the function pointer declaration as the array’s type, as shown below. The array is also initialized to all NULLs. When a block of initialization values are used with an array, its values will be assigned to consecutive elements of the array. If the number of values is less than the size of the array, the value is used to initialize every element of the array.
typedef int (*operation)(int, int);
operation operations[128] = {NULL};
Alternatively, we can declare this array without using a typedef as:
int (*operations[128])(int, int) = {NULL};
The intent of this array is to allow a character index to select a corresponding function to execute. For example, the '*' character will identify the multiplication function if it exists. We can use character indexes because a character literal is an integer. The 128 elements corresponds to the first 128 ASCII characters.
Having initialized the array to all NULLs, we then assign the add and sub functions to the elements corresponding to the plus and minus signs:
void initializeOperationsArray() {
operations['+'] = add;
operations['-'] = sub;
}
The previous evaluate function is rewritten as evaluateArray. Instead of calling the select function to obtain a function pointer, we used the operations with the operation character as an index:
int evaluateArray(char opcode, int num1, int num2) {
fptrOperation operation;
operation = operations[opcode];
return operation(num1, num2);
}
initializeOperationsArray();
printf("%d\n", evaluateArray('+', 6, 5));
printf("%d\n", evaluateArray('-', 6, 5));
Output:
11
1
A more robust version of the evaluateArray function would check for null function pointers before trying to execute the function.
Comparing Function Pointers
Function pointers can be compared to one another using the equality and inequality operators.
fptrOperation fptr1 = add;
if(fptr1 == add) {
printf("fptr1 points to add function\n");
}
else {
printf("fptr1 doesn't point to add function\n");
}
A more realistic example of where the comparison of function pointers would be useful involves an array of function pointers that represent the steps of a task. For example, we may have a series of functions that manipulate an array of inventory parts. One set of operations may be to sort the parts, calculate a cumulative sum of their quantities, and then display the array and sum. A second set of operations may be to display the array, find the most expensive and the least expensive, and then display their difference. Each operation could be defined by an array of pointers to the individual functions. A log operation may be present in both lists. The ability to compare two function pointers would permit the dynamic modification of an operation by deleting the operation, such as logging, by finding and then removing the function from the list.
Casting Function Pointers
A pointer to one function can be cast to another type. This should be done with care since the runtime system does not verify that parameters used by a function pointer are correct. It is also possible to cast a function pointer to a different function pointer and then back. The resulting pointer will be equal to the original pointer. The size of function pointers used are not necessarily the same.
typedef int (*fptrToSingleInt)(int);
typedef int (*fptrToTwoInts)(int,int);
int add(int, int);
fptrToTwoInts fptrFirst = add;
fptrToSingleInt fptrSecond = (fptrToSingleInt)fptrFirst;
fptrFirst = (fptrToTwoInts)fptrSecond;
printf("%d\n",fptrFirst(6,5));
Output:
11
Conversion between function pointers and pointers to data is not guaranteed to work.
void* with Function Pointers
The use of void*
is not guaranteed to work with function pointers. That is, we should not assign a function pointer to void* as shown below:
void* pv = add;
However, when interchanging function pointers, it is common to see a “base” function pointer type as declared below. This declares fptrBase as a function pointer to a function, which is passed void and returns void:
typedef void (*fptrBase)();
fptrBase basePointer;
fptrFirst = add;
basePointer = (fptrToSingleInt)fptrFirst;
fptrFirst = (fptrToTwoInts)basePointer;
printf("%d\n",fptrFirst(6,5));
A base pointer is used as a placeholder to exchange function pointer values.
Always make sure you use the correct argument list for function pointers. Failure to do so will result in indeterminate behavior.