DEV Community

Ahmed Gouda
Ahmed Gouda

Posted on • Originally published at ahmedgouda.hashnode.dev

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)();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Using a Function Pointer

int (*fptr1)(int);

int square(int num) {
    return num * num;
}
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

Output:

5 squared is 25
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode
printf("%d\n", compute(add, 6, 5));
printf("%d\n", compute(sub, 6, 5));
Enter fullscreen mode Exit fullscreen mode

Output:

11
1
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode
printf("%d\n", evaluate('+', 6, 5));
printf("%d\n", evaluate('-', 6, 5));
Enter fullscreen mode Exit fullscreen mode

Output:

11
1
Enter fullscreen mode Exit fullscreen mode

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};
Enter fullscreen mode Exit fullscreen mode

Alternatively, we can declare this array without using a typedef as:

int (*operations[128])(int, int) = {NULL};
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode
initializeOperationsArray();
printf("%d\n", evaluateArray('+', 6, 5));
printf("%d\n", evaluateArray('-', 6, 5));
Enter fullscreen mode Exit fullscreen mode

Output:

11
1
Enter fullscreen mode Exit fullscreen mode

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");
}
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

Output:

11
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)();
Enter fullscreen mode Exit fullscreen mode
fptrBase basePointer;
fptrFirst = add;
basePointer = (fptrToSingleInt)fptrFirst;
fptrFirst = (fptrToTwoInts)basePointer;
printf("%d\n",fptrFirst(6,5));
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (2)

Collapse
 
pauljlucas profile image
Paul J. Lucas • Edited

Below, we declare a pointer to a function that is passed void and returns void:

void (*foo)();

No. In C, an empty parameter list means nothing is specified about what is passed to the function for backwards compatibility with K&R C. To specify that the function has no parameters, you need to do:

void (*f)(void);
Enter fullscreen mode Exit fullscreen mode

You should also mention that you can call a pointed to function explicitly since it occurs in a lot of code this way:

printf("%d  squared is %d\n", n, (*fptr1)(n));
Enter fullscreen mode Exit fullscreen mode

You should also mention that you can have a typedef of function; see here.

You should also mention that you can pass a pointer to function as a parameter without a typedef:

int compute(int (*operation)(int, int), int num1, int num2);
Enter fullscreen mode Exit fullscreen mode

even though it's generally recommended that you use a typedef to make things more readable.

Your select function doesn't return anything if the argument isn't either a + or -: you should handle that case.

Collapse
 
ahmedgouda profile image
Ahmed Gouda

Thanks very much for your comment, it is really valuable.