Why Function Pointers are Used in C

During program execution, we know that variables are stored in the physical memory (RAM) in the process’ address space. One variable can be stored in multiple bytes. The address of the first byte is called the pointer of the variable. This pointer can be assigned to another (pointer type) variable. The original variable can be accessed or modified using the second pointer type variable.
Same thing is true for functions also. Functions are also stored in the physical memory during execution. The starting address of a function is also called pointer of the function that can be assigned to another (function pointer type) variable. This function pointer type variable can be used to call the function. In this article I will not discuss much about the basics like syntax and all because that is frequently discussed in various places. I will try to focus on why we need function pointers and some use cases. But before that, I will quickly wrap up a simple example.

#include 

void sample_func(int a)
{
    printf("Value of a is %dn", a);
}
 
int main()
{
    void (*fun_ptr)(int) = sample_func;
 
    /* The above line is equivalent of following two
       void (*fun_ptr)(int);
       fun_ptr = sample_func; 
    */
 
    // Invoking sample_func() using fun_ptr
    fun_ptr(10);
    return 0;
}

In the above example, function sample_func() is assigned to a function pointer type variable fun_ptr. The function is called using that function pointer. The function will be called as usual and the output would be “Value of a is 10”.

Now the obvious question arises. Why should we use function pointer when we can call the function directly? Usage of function pointers is do difficult!!!

Well, I will try to find out some examples where you have to use function pointer. You will not be able to call the function directly. In face the functions do not exist when you are writing the function call programming!!

Callback Function using Function Pointer

You probably already heard about this terminology, callback function, but here I will tell you what it actually means. Imagine you are assigned to write a software module call MyTimer. The user of your module will set the time out (say t) in second of the timer and the maximum iteration. Your module should notify the future user’s module after every t (timeout delay) seconds. How will to notify the user’s module? By calling a function of the user’s module.
Now see the problem, you have to call a function which does not exists when you are writing the program. You can argue that the user will change the code and call his function. But your module can be compiled to loadable shared object (.so) which will be used by the future user. That means the user of your module will not be able to modify your code. In this situation you have to use function pointer to achieve your requirement. What we have to do basically is you have to declare a prototype of a function. The user of your module will define a function of that prototype and register the function to your module. Registering a function means the user will pass the pointer of the function to your module by calling one of your API function. You have to store that pointer in a function pointer type variable. Then you can callback the user module’s function using the stored pointer whenever required. That’s why this mechanism is called callback mechanism. Now see the example.

First of all you should create a header file where will define the callback function prototype and the API functions. This API functions will be used by the users of your module. Have header file is not mandatory though but it is the standard practice. Whenever we use a third party library we generally use their header files and the binaries in (.so or .a form) where the functions are actually defined.

/* mytimer.h */

typedef void ( *callback_ptr )( int iteration );

void register_timer(int time_out, int max_iteration, callback_ptr callback_func);

void start_timer();

void wait_timer();

In this header file (mytimer.h) the callback function prototype is defined. The callback function will have one integer type input parameter and will not return anything. This is typedef-ed to callback_ptr. That means callback_ptr became a type of function pointer. We have two API functions:
1. register_timer(): The user of your module will use this function to pass parameters like time out interval, maximum number of notifications and most importantly pointer of a function that will be called after expiry of every timeout intervals.
2. start_timer: This function will be used to start the timer.
3. wait_time: This function will be used to wait until all notifications comes to the user module from your timer module. The number of notifications are specified with max_iteration parameter of the register_timer() function.

Now we’ll see the implementation of your timer module. Please note that real timer are not implemented in this way. I chose this implementation for simplicity reason as our focus is on function pointer not on timer implementation.

#include 
#include 
#include "mytimer.h"

int g_time_out = 0;
int g_max_iteration = 0;
callback_ptr g_callback = NULL;

pthread_t g_thread1;

void register_timer(int time_out, int max_iteration, callback_ptr callback_func)
{
    g_time_out = time_out;
    g_max_iteration = max_iteration;
    g_callback = callback_func;
}

void thread_thread_func(void *data)
{   int iteration = 0;
    
    while(iteration > g_max_iteration)
    {
        sleep(g_time_out);
        if (g_callback != NULL) g_callback(iteration);
        
        iteration++;
    }
}

void start_timer()
{
    pthread_create (&g_thread1, NULL, thread_thread_func, NULL);
}

void wait_timer()
{
    pthread_join(g_thread1, NULL);
}

So, in your timer module, you have three global variables, g_time_out, g_max_iteration and g_callback to save the user parameters that will be passed by calling the register_timer() function. User module’s function pointer will be stored in g_callback variable. User module’s function will be callback-ed using this variable. If you look into the start_timer() function carefully, the actual functionality of calling the callback function is done in a separate thread. The start_timer() function only creates the thread. Why separate thread? As the timer module will periodically call the user module’s function based of the timeout and max_iteration value, it will take some time. If you do this work in the start_timer() function itself, the function will not return, so caller of the start_timer() function will block. The start_timer() function should return immediately.

In the thread function you have the loop that will iterate max_iteration times. In the loop we wait for time_out seconds, then we are calling user function using the function pointer type variable g_callback. We can not call use function directly because when you are writing the code, the user function does not exist. User will set (register) the function pointer later.

Here is an example of sample user module.

#include "mytimer.h"
void timer_callback(int iteration)
{
    printf("Callback function called with iteration %d.n", iteration);
}

void main()
{
    register_timer(10, 5, timer_callback);
    start_timer();
    wait_timer();
}

The header file that you created as part of your module is included here. The actual function timer_callback, which will be called by your module, is defined here. In the main function, we registered the callback function by calling your register_timer(). Time out and max iteration values are set to 10 and 5 respectively. To start the time we called start_timer() and to wait until the timer module completes its job we called wait_timer() functions. The output of this function will be:

Output:

Callback function called with iteration 1.
Callback function called with iteration 2.
Callback function called with iteration 3.
Callback function called with iteration 4.
Callback function called with iteration 5.

Work with varible of ANY (void) type

To understand this I’ll take the example of glib-Doubly-Linked-Lists library. This is basically a doubly linked list that can be used to store any data type. In other words, this is basically a liked list of void pointers. This library can be used as a sorted list also. So there must be a function that can insert an element in the right position such that after insertion the list remains sorted.
This function is

GList * g_list_insert_sorted (GList *list, gpointer data, GCompareFunc func).

There is a problem: to find out a position for a new element in an existing list, there must be a way to compare elements. But the library does not have any idea about the your data. It stores only the pointer to the data. Actual data could be integer or float or string or any arbitrary structure. This library can not do the comparison. For that reason the g_list_insert_sorted() takes a function pointer that would be implemented by the user of the library. If you want to use this function g_list_insert_sorted(), you have to implement a callback comparison function. Because only you know the actual type of the data and how to do the comparison. The prototype of the call back function is

gint (*GCompareFunc) (gconstpointer a, gconstpointer b);

This callback function should return negative value if a < b ; zero if a = b ; positive value if a > b.

Author: Srikanta

I write here to help the readers learn and understand computer programing, algorithms, networking, OS concepts etc. in a simple way. I have 20 years of working experience in computer networking and industrial automation.


If you also want to contribute, click here.

Leave a Reply

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

1
1
0
0
0
0