Function Pointer in C

Function Pointer in C. Function Pointer in C Tutorial. Learn Function Pointer in C
Function Pointer in C

What is Function Pointer in C ?

A function pointer in C is a variable that stores the address of a function. It allows you to treat functions as data and pass them as arguments to other functions or assign them to variables. Function pointers provide a way to achieve dynamic or runtime polymorphism in C.

The syntax of a function pointer declaration in C is as follows:

return_type (*pointer_name)(parameter_list);

Function pointers are used in C for various purposes, including:

  1. Callback functions: Function pointers can be passed as arguments to other functions, allowing those functions to call the specified function at a later point in the program execution. This is often used for event handling or implementing customizable behavior.
  2. Implementing data structures and algorithms: Function pointers can be used to create data structures such as function tables or arrays of functions. This allows different functions to be selected and executed dynamically based on specific conditions or inputs.

To declare a function pointer, you need to specify the function signature it points to.Here’s an example of declaring and calling a function pointer:

#include <stdio.h>

void sayHello() {
    printf("Hello, World!\n");
}

int main() {
    void (*ptr)() = sayHello; // Declare function pointer and assign the address of sayHello
    ptr(); // Call the function using the pointer
    return 0;
}

Output:

Hello, World!

Explanation:

In above example, we declare a function sayHello that prints “Hello, World!” when called. Then, in the main function, we declare a function pointer ptr using the syntax void (*ptr)(). We assign the address of the sayHello function to ptr using the assignment operator =. Finally, we call the function using the function pointer ptr().

Different ways to use Function Pointer in C with examples!

1. Callback Functions

Function pointers are often used as callback functions, allowing you to specify custom behavior that will be called at a specific point in your program. This is commonly used in event handling systems or libraries where the exact behavior needs to be customizable. By passing a function pointer as an argument to another function, you can provide a callback function that gets executed when certain conditions are met.

Example: Implementing a callback function for event handling.

#include <stdio.h>

void eventHandler(void (*callback)()) {
    printf("Event occurred!\n");
    callback();
}

void callbackFunction() {
    printf("Callback function called!\n");
}

int main() {
    eventHandler(callbackFunction);
    return 0;
}

Output:

Event occurred!
Callback function called!

Explanation:

In above example, we have an eventHandler function that takes a function pointer callback as an argument. When an event occurs, the eventHandler function is called and it executes the callback function provided as an argument. In the main function, we pass callbackFunction as the callback to eventHandler. When the event occurs, the callback function is called and its corresponding message is printed.

2. Function Tables

Function pointers can be used to create arrays or tables of functions. This technique is often used when you have multiple functions with similar behavior, and you want to select and execute the appropriate function dynamically. By storing the function pointers in an array or a table, you can choose the desired function based on specific conditions or user input.

Example: Using a function table to perform different operations on integers.

#include <stdio.h>

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

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

int multiply(int a, int b) {
    return a * b;
}

int divide(int a, int b) {
    if (b != 0)
        return a / b;
    else
        return 0;
}

typedef int (*MathOperation)(int, int);

int main() {
    MathOperation operations[] = {add, subtract, multiply, divide};
    int choice, a, b;

    printf("Enter two numbers: ");
    scanf("%d %d", &a, &b);

    printf("Enter the operation (0:add, 1:subtract, 2:multiply, 3:divide): ");
    scanf("%d", &choice);

    int result = operations[choice](a, b);
    printf("Result: %d\n", result);

    return 0;
}

Output:

Enter two numbers: 10 5
Enter the operation (0:add, 1:subtract, 2:multiply, 3:divide): 2
Result: 50

Explanation:

In above example, we define four mathematical operations: add, subtract, multiply, and divide. We create a function table operations using a typedef MathOperation, which is an array of function pointers. The user selects an operation using an integer choice. Based on the choice, the corresponding function from the operations array is called with the provided numbers a and b, and the result is displayed.

3. Dynamic Function Dispatch

Function pointers allow you to implement runtime polymorphism, where the behavior of a program can be determined at runtime based on the actual type or conditions. By using function pointers, you can switch between different functions dynamically, enabling flexible and extensible program behavior.

Example: Implementing dynamic function dispatch using a function pointer.

#include <stdio.h>

typedef void (*DynamicFunction)();

void functionA() {
    printf("This is function A\n");
}

void functionB() {
    printf("This is function B\n");
}

void functionC() {
    printf("This is function C\n");
}

int main() {
    DynamicFunction func;

    int choice;
    printf("Enter the function number (1:A, 2:B, 3:C): ");
    scanf("%d", &choice);

    switch (choice) {
        case 1:
            func = functionA;
            break;
        case 2:
            func = functionB;
            break;
        case 3:
            func = functionC;
            break;
        default:
            printf("Invalid choice!\n");
            return 1;
    }

    func(); // Dynamic function dispatch

    return 0;
}

Output:

Enter the function number (1:A, 2:B, 3:C): 2
This is function B

Explanation:

In above example, we have three functions: functionA, functionB, and functionC. We define a typedef DynamicFunction for a function pointer type. The user selects a function by entering a number, and based on the choice, the corresponding function is assigned to the func function pointer. Finally, the function is called using func(), resulting in the execution of the selected function.

4. Sorting and Comparisons

Function pointers are useful in sorting algorithms where the comparison criteria need to be customizable. By passing a function pointer that defines the comparison logic, you can sort data based on different criteria without modifying the sorting algorithm itself. This approach allows for greater code reuse and flexibility.

Example: Sorting an array using a function pointer for comparison.

#include <stdio.h>

int ascendingOrder(int a, int b) {
    return a - b;
}

int descendingOrder(int a, int b) {
    return b - a;
}

void bubbleSort(int arr[], int size, int (*compare)(int, int)) {
    int i, j, temp;
    for (i = 0; i < size - 1; i++) {
        for (j = 0; j < size - i - 1; j++) {
            if (compare(arr[j], arr[j + 1]) > 0) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main() {
    int numbers[] = {5, 2, 8, 1, 9};
    int size = sizeof(numbers) / sizeof(numbers[0]);

    printf("Original array: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", numbers[i]);
    }
    printf("\n");

    bubbleSort(numbers, size, ascendingOrder);

    printf("Ascending order: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", numbers[i]);
    }
    printf("\n");

    bubbleSort(numbers, size, descendingOrder);

    printf("Descending order: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", numbers[i]);
    }
    printf("\n");

    return 0;
}

Output:

Original array: 5 2 8 1 9
Ascending order: 1 2 5 8 9
Descending order: 9 8 5 2 1

Explanation:

In above example, we have two comparison functions, ascendingOrder and descendingOrder, which define the sorting order. The bubbleSort function takes an array, its size, and a function pointer compare as arguments. The function pointer compare is used to determine the order of comparison in the sorting algorithm. By passing different comparison functions to bubbleSort, we can achieve different sorting orders. The array numbers is sorted first in ascending order and then in descending order.

5. Function Wrappers

Function pointers can be used to wrap functions with additional behavior or to provide hooks for pre- and post-processing. By wrapping functions with function pointers, you can add logging, error handling, performance profiling, or other functionality without modifying the original function implementation.

Example: Wrapping a function with additional behavior using function pointers.

#include <stdio.h>

void originalFunction() {
    printf("This is the original function.\n");
}

void wrapperFunction(void (*original)()) {
    printf("Before calling the original function.\n");
    original();
    printf("After calling the original function.\n");
}

int main() {
    wrapperFunction(originalFunction);
    return 0;
}

Output:

Before calling the original function.
This is the original function.
After calling the original function.

Explanation:

In above example, we have an originalFunction that performs a specific task. The wrapperFunction takes a function pointer original as an argument, wraps additional behavior before and after calling the original function, and then calls the original function. In the main function, we pass originalFunction as the argument to wrapperFunction, resulting in the execution of both the wrapper and the original function.

6. State Machines

Function pointers can be used to implement state machines where each state corresponds to a specific function. By assigning the appropriate function pointer based on the current state, you can define the behavior of the state machine dynamically.

Example: Implementing a simple state machine using function pointers.

#include <stdio.h>

typedef void (*StateFunction)();

void stateOne() {
    printf("State One\n");
}

void stateTwo() {
    printf("State Two\n");
}

void stateThree() {
    printf("State Three\n");
}

int main() {
    StateFunction currentState = stateOne;

    // Transition from stateOne to stateTwo
    printf("Transition from stateOne to stateTwo:\n");
    currentState = stateTwo;
    currentState();

    // Transition from stateTwo to stateThree
    printf("Transition from stateTwo to stateThree:\n");
    currentState = stateThree;
    currentState();

    return 0;
}

Output:

Transition from stateOne to stateTwo:
State Two
Transition from stateTwo to stateThree:
State Three

Explanation:

In above example, we have three state functions: stateOne, stateTwo, and stateThree. We define a typedef StateFunction for a function pointer type. The currentState function pointer represents the current state of the state machine. By assigning different state functions to currentState, we simulate transitions between states. In this example, we transition from stateOne to stateTwo and then from stateTwo to stateThree.

7. Dynamic Library Loading

Function pointers are essential when working with dynamically loaded libraries (DLLs) or shared objects (SOs) in C. Function pointers are used to bind and invoke functions within the loaded library, allowing your program to use functions that are not linked at compile time.

Example: Loading a dynamic library and using function pointers to call its functions.

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

typedef int (*AddFunction)(int, int);

int main() {
    void* handle = dlopen("./libmath.so", RTLD_LAZY);
    if (!handle) {
        fprintf(stderr, "Failed to load the library: %s\n", dlerror());
        return 1;
    }

    AddFunction add = (AddFunction)dlsym(handle, "add");
    if (!add) {
        fprintf(stderr, "Failed to find the symbol: %s\n", dlerror());
        dlclose(handle);
        return 1;
    }

    int result = add(10, 5);
    printf("Result: %d\n", result);

    dlclose(handle);
    return 0;
}

Output:

Result: 15

Explanation:

In above example, we load a dynamic library named libmath.so using the dlopen function from the dlfcn.h header. We obtain a handle to the loaded library, and then we use dlsym to retrieve the function pointer to the add function defined in the library. We cast the function pointer to the correct function type, and finally, we call the add function through the function pointer. The result is displayed, and the library is closed using dlclose.

More: