Function Pointer in C – Complete Guide with Examples

A function pointer in C is one of the most powerful and often misunderstood features of the language. Unlike a normal pointer (int *, char*, …etc.) that stores the address of a variable, a function pointer stores the address of a function.

In C function pointers are not just an advanced topic but they represent one of the most powerful and flexible features of the language. From operating systems and firmware architectures to RTOS hooks and driver callbacks, function pointers enable dynamic behavior, decoupled design, and runtime flexibility.

Yet many C programmers struggle with function pointers because of confusion around their declaration, initialization, and dereferencing.
But once understood, they enable dynamic function calls, callback mechanisms, and even object-oriented-like behavior in C.

 

 

A function pointer in C is one of the most powerful yet often misunderstood features of the language. Unlike regular pointers (such as int* or char*) that store the address of a variable, a function pointer stores the address of a function itself.

In C, function pointers are not just an advanced topic; they represent one of the most flexible and expressive mechanisms for building dynamic and modular programs. From operating systems and firmware architectures to RTOS hooks and driver callbacks, function pointers enable runtime flexibility, decoupled design, and event-driven behavior.

Over the years, I have used function pointers in multiple layers of embedded systems — including bootloaders, hardware drivers, and application frameworks — to achieve clean abstractions and dynamic behavior.

Yet, many C programmers struggle with them due to confusion around declaration, initialization, and dereferencing. Once mastered, however, function pointers open the door to dynamic function calls, callback mechanisms, and even object-oriented-like programming in pure C.

In this blog post, I will cover:

  • How to declare, initialize, and call a function pointer.
  • Common mistakes and best practices.
  • Real-world examples: callbacks, function pointer arrays, and state machines.

 

What is a Function Pointer?

A function pointer is a special type of pointer variable that holds the address of a function (executable code) instead of a data object. Just like you can use a pointer to access a variable indirectly, you can use a function pointer to call (invoke) a function indirectly through its address.

It allows you to select and execute different functions dynamically at runtime, making your code more flexible, modular, and easier to maintain.

You can see in the image below that the function pointer fnptr can point to either Function1() or Function2(), depending on a specific condition or situation. This demonstrates how function pointers enable dynamic selection of functions at runtime.

function pointer

 

 

Why Use Function Pointers?

Function pointers add flexibility and abstraction to C programs, making them an essential tool in embedded system development.

We commonly use function pointers to:

  • Implement callbacks for event-driven programming.
  • Create plugin-like architectures for modular code.
  • Replace complex switch-case logic with cleaner, dynamic function selection
  • Build state machines for predictable and maintainable workflows.
  • Design hardware abstraction layers (HALs) for portable and scalable firmware

It enables runtime reconfiguration, which is crucial for building modular, scalable, and maintainable embedded systems.

 

Example:

In this example, we define a typedef pLEDAction for a function pointer that points to any function returning void and taking no arguments. The controlLED function accepts this function pointer as a callback, allowing us to dynamically decide at runtime whether to turn the LED ON or OFF.

#include <stdio.h>


/*Typedef for a function pointer that points to a function
returning void and taking no arguments*/
typedef void (*pLEDAction)();


void ledOn()
{
    printf("LED is ON\n");
}

void ledOff()
{
    printf("LED is OFF\n");
}



// Function that accepts a function pointer as a callback
void controlLED(pLEDAction ledAction)
{
    // Call the function via the pointer
    ledAction();
}

int main()
{
    // Using the typedef for function pointer
    pLEDAction fnptr;

    // Example 1: Point to ledOn
    fnptr = ledOn;
    controlLED(fnptr);  // Output: LED is ON

    // Example 2: Point to ledOff
    fnptr = ledOff;
    controlLED(fnptr);  // Output: LED is OFF

    return 0;
}

Output:

LED is ON
LED is OFF

 

How to Declare a Function Pointer in C?

The syntax for declaring function pointers is actually quite straightforward. While it may seem confusing at first, once you become familiar with function pointers, it becomes much easier to use them.

Function pointer declarations are very similar to regular function declarations: you need to specify the return type, the argument list, and the name of the pointer.

Let’s take a closer look at the syntax for declaring a function pointer.

ReturnType (*FunctionPointerName)(ArgumentTypeList);

Example:

// Function pointer that can point to a function taking an int and returning void
void (*fpData)(int);


// Function pointer that can point to a function taking a const char* and returning void
void (*pfDisplayMessage)(const char *);

Explanation:

  • fpData is a function pointer that can point to any function that takes an integer argument and returns nothing (void).
  • pfDisplayMessage is a function pointer that can point to any function that takes a string (const char*) argument and returns nothing (void).

💡 Very important points:  Always use parentheses around the pointer name. Without them, you would declare a function returning a pointer not a pointer to a function.

void (*fpData)(int) → pointer to a function taking int and returning void. ✅

Memory: fpData ----> [Function code that takes const char*]

void *fpData(int) → function returning a pointer, not a function pointer. ❌

Memory: fpData() ----> returns a pointer

 

List of Some Function Pointers:

A function pointer must have the same signature as the function it points to. In simple terms, the parameters and return type of the function pointer must match those of the target function exactly.

C allows for a wide variety of function pointer declarations, ranging from simple to highly complex. In the section below, we list several examples of function pointers, along with detailed explanations in the comments to help you understand their meaning and usage.

// Function pointer taking no arguments and returning void
void (*fpData)(void);

// Function pointer taking an int and returning int
int  (*fpData)(int a);

// Function pointer taking a char* and returning int
int  (*fpData)(char *s);

// Function pointer taking a char* and returning int*
int* (*fpData)(char *s);

// Function pointer taking int and char* arguments and returning int
int  (*fpData)(int a, char *s);

// Function pointer taking int, int*, and char* arguments, returning int*
int* (*fpData)(int a, int *b, char *s);

// Function pointer taking int, char, and a pointer to an array of 3 ints, returning int*
int* (*fpData)(int a, char c, int (*arr)[3]);

// Function pointer taking int, pointer to array of 3 ints, 
// and a function pointer (const char* -> int), returning int*
int* (*fpData)(int a, int (*arr)[3], int (*fptr)(const char*));

// Function pointer taking pointer to array of 3 ints, 
// a function pointer (const char* -> int), 
// and an array of 3 function pointers (const char* -> int), returning int*
int* (*fpData)(int (*arr)[3], int (*fptr)(const char*), int (*farr[3])(const char*));

// Array of 2 function pointers, each taking pointer to array of 3 ints, 
// a function pointer (const char* -> int), 
// and an array of 3 function pointers (const char* -> int), returning int*
int* (*fpData[2])(int (*arr)[3], int (*fptr)(const char*), int (*farr[3])(const char*));

// Function pointer taking a char* and returning a function pointer
// which itself takes pointer to array of 3 ints, a function pointer (char* -> int), 
// and an array of 3 function pointers (char* -> int), returning int*
int* (*(*fpData)(char *s))(int (*arr)[3], int (*fptr)(char*), int (*farr[3])(char*));

 

Initializing a Function Pointer:

After declaring the function pointer, you need to initialize it with the address of the target function.
There are two equivalent ways to do this:

pfAddTwoNumber = &AddTwoNumber; // Using address-of operator

or simply.

pfAddTwoNumber = AddTwoNumber; // Function name represents its address

Both statements assign the address of the function AddTwoNumber() to the pointer pfAddTwoNumber.

 

Initialization at Declaration Time:

Just like normal pointers, you can initialize a function pointer at the time of declaration. It improves the code readability:

int (*pfAddTwoNumber)(int, int) = AddTwoNumber;

This single line both declares and initializes the function pointer.

 

Let’s look at an example that demonstrates how to declare and initialize a function pointer in C. It also shows how the function pointer can be used to invoke the function it points to.

#include <stdio.h>

// A function that takes a const char* argument and returns void
void DisplayMessage(const char *msg)
{
    printf("Message  =>>  %s\n", msg);
}



int main(void)
{
    // Declare a function pointer that can point to a function
    // taking a 'const char *' argument and returning 'void'
    void (*pfDisplayMessage)(const char *);

    // Initialize the pointer with the address of DisplayMessage()
    pfDisplayMessage = &DisplayMessage;

    // Call the function using the function pointer
    (*pfDisplayMessage)("Hello Aticleworld.com");

    // You can also omit the '*' and '&' (they are optional in this context)
    // pfDisplayMessage("Hello Aticleworld.com");

    return 0;
}

Output: Message =>> Hello Aticleworld.com

 

Some important concept related to pointer to function:

Let’s go through some important concepts related to function pointers in C that every firmware and system-level engineer should know. Understanding these concepts will help you avoid common pitfalls, write safer and more maintainable code, and use function pointers effectively in both embedded and application-level development.

1. Memory Allocation and Deallocation for Function Pointers:

Dynamic memory allocation is not useful for function pointers. A function pointer only stores the address of a function, not dynamically allocated data.

Hence, the following example makes no sense:

❌ Not useful
void (*pfData)(int) = malloc(sizeof(pfData));

✅ Correct usage:
Just declare the function pointer and assign it the address of a function:

void (*pfData)(int) = MyFunction;

 

2. Comparing Function Pointers:

You can compare function pointers using comparison operators (==, !=).

These checks are essential for reliability and crash prevention, especially in embedded and kernel code.

For example:

if (pfLedOnOff != NULL)
{
    (*pfLedOnOff)(iLedState);
}
else
{
    return Invalid;
}

🧩 Important Points:

  • Two function pointers are equal only if both are NULL or both point to the same function.
  • Always initialize your function pointer with NULL at declaration.
  • Accessing an uninitialized function pointer may cause system crashes or BSODs (Blue Screen of Death) in drivers.

 

3. Assigning Function Address to a Function Pointer:

There are two equivalent ways to assign a function address:

  • Function_Pointer = Function_Name; // ✅ Direct assignment
  • Function_Pointer = &Function_Name; // ✅ Using & operator

Both are valid because the function name itself represents its starting address.

 

4. Calling a Function Using a Function Pointer:

Once the pointer is initialized, you can call the function in two ways:

iRetValue = (*pfAddTwoNumber)(10, 20); // With dereference

// or simply

iRetValue = pfAddTwoNumber(10, 20); // Cleaner and equivalent

#include <stdio.h>

//function used to add two numbers
int AddTwoNumber(int iData1,int iData2)
{
    return (iData1 + iData2);
}


int main()
{
    int iRetValue = 0;
    
    //Declaration of function pointer
    int (*pfAddTwoNumber)(int,int) = NULL;
    
    //initialize the function pointer
    pfAddTwoNumber = AddTwoNumber;
    
    //Calling the function using the function pointer
    iRetValue = (*pfAddTwoNumber)(10,20);
    
    //display addition of two number
    printf("\n\nAddition of two number = %d\n\n",iRetValue);
    
    return 0;
}

Output:

 

 

 

Explanation of the code:

1. Declaration of function pointer:

int (*pfAddTwoNumber)(int, int) = NULL;

Here, pfAddTwoNumber is a pointer that can point to any function taking two int arguments and returning an int.

 

2. Initialization with the function address:

pfAddTwoNumber = AddTwoNumber;

The name of a function in C represents its base address, so this assignment is perfectly valid.

 

3. Calling the function:

You can call the function through the pointer in either of the following ways:

iRetValue = (*pfAddTwoNumber)(10, 20);

or simply

iRetValue = pfAddTwoNumber(10, 20);

Both are equivalent in C; the compiler automatically applies the dereference when using the pointer in a function-call context.

Note: The parentheses around (*pfAddTwoNumber) are important for operator precedence. However, when calling a function, the dereference operator * can safely be omitted because C automatically interprets pfAddTwoNumber() as a function call through the pointer.

 

5) Passing Function Pointers as Arguments in C:

In C, function pointers are not just for storing addresses—they can also be passed as arguments to other functions. This enables dynamic behavior: a single function can perform different operations depending on the function pointer it receives.

This technique is widely used in callbacks, event handling, and modular programming, making your code more flexible, reusable, and maintainable.

Why Pass a Function Pointer?

Passing a function pointer allows a function to call another function indirectly. For example, you can create a generic arithmetic function that can perform addition, subtraction, multiplication, or division based on the function pointer you provide, instead of writing separate functions for each operation.

Example: Dynamic Arithmetic Operations

Here’s a practical example demonstrating how to use a function pointer as an argument:

#include <stdio.h>

// Typedef for a function pointer to simplify code
typedef int (*pfunctPtr)(int, int);

// Generic arithmetic function
int ArithMaticOperation(int a, int b, pfunctPtr operation)
{
    return operation(a, b);  // Call the passed function
}

// Arithmetic operation functions
int AddTwoNumber(int a, int b)
{
    return a + b;
}

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

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

int main(void)
{
    int num1, num2, choice, result;
    printf("Enter two Integer Data \n\n");
    scanf("%d%d",&num1,&num2);

    printf("\nSelect operation:\n");
    printf("Enter 1 for Addition \n\n");
    printf("Enter 2 for Subtraction \n\n");
    printf("Enter 3 for Multiplication \n\n");

    printf("User choice :");
    scanf("%d",&choice);

    switch (choice)
    {
    case 1:
        result = ArithMaticOperation(num1, num2, AddTwoNumber);
        break;
    case 2:
        result = ArithMaticOperation(num1, num2, SubTwoNumber);
        break;
    case 3:
        result = ArithMaticOperation(num1, num2, MulTwoNumber);
        break;
    default:
        printf("\nInvalid choice!\n");
        return 0;
    }

    printf("\nResult = %d\n", result);
    return 0;
}

Output:

 

 

How It Works?

1. Define a Function Pointer Type

typedef int (*pfunctPtr)(int, int);

pfunctPtr is now a type alias for any function taking two integers and returning an integer.

 

2. Pass Function Pointer to Another Function

ArithMaticOperation(num1, num2, AddTwoNumber);

Here, AddTwoNumber is passed as a pointer. The ArithMaticOperation function calls it internally.

 

3. Dynamic Execution
The behavior of ArithMaticOperation depends on the function pointer provided. This avoids duplicating logic for different operations.

 

Benefits:

✅ Code Reusability: One generic function handles multiple operations.
✅ Flexibility: Easily extendable; add new operations without changing existing code.
✅ Modularity: Encourages clean, maintainable design.
✅ Real-World Use: Foundation for callbacks, plugin systems, and event-driven programming in embedded and application-level C.

Pro Tip:

Whenever you find yourself writing multiple functions with the same structure but different logic, consider passing a function pointer to a generic function. It drastically reduces code duplication and improves maintainability.

 

6) Returning a Function Pointer from a Function:

In C, it is possible for a function to return a pointer to another function. This technique is widely used in callback mechanisms, dynamic function selection, or implementing table-driven designs.

Using a typedef makes the syntax much cleaner and easier to read.

Here is an example:

This program demonstrates how to return a function pointer from a function to select different arithmetic operations dynamically.

#include <stdio.h>

/*Typedef for a function pointer 
that takes two ints and returns an int*/
typedef int (*pfunctPtr)(int, int);

/* Arithmetic functions */
int AddTwoNumber(int a, int b)
{
    return a + b;
}

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

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

/* Function that returns a 
function pointer based on user's choice.*/
pfunctPtr ArithMaticOperation(int choice)
{
    switch(choice)
    {
    case 1:
        return AddTwoNumber;
    case 2:
        return SubTwoNumber;
    case 3:
        return MulTwoNumber;
    default:
        return NULL;  // invalid choice
    }
}

int main()
{
    int num1, num2, choice;
    int result;

    printf("Enter two integers: ");
    scanf("%d %d", &num1, &num2);

    printf("Choose operation:\n");
    printf("1: Addition\n2: Subtraction\n3: Multiplication\n");
    scanf("%d", &choice);

    /* Get function pointer based on user choice */
    pfunctPtr opFunc = ArithMaticOperation(choice);

    if (opFunc != NULL)
    {
        result = opFunc(num1, num2); // call via function pointer
        printf("Result = %d\n", result);
    }
    else
    {
        printf("Invalid choice! Please enter 1, 2, or 3.\n");
    }

    return 0;
}

Output:

 

7) Use of Array of Function Pointers:

An array of function pointers is essentially a collection of pointers where each pointer points to a function. This allows you to call different functions dynamically using an index, which is especially useful for implementing menus, command handlers, or dispatch tables in embedded and system-level programming.

Key points:

  • The signature of each function in the array must match the signature of the function pointers.
  • You can call a function using the syntax:

(*array_name[index])(arguments);

or simply.

array_name[index](arguments);

 

Example: Arithmetic Operations

In this example, we create an array of function pointers for three arithmetic operations: addition, subtraction, and multiplication.

#include <stdio.h>

// Arithmetic function prototypes
int AddTwoNumber(int a, int b)
{
    return a + b;
}
int SubTwoNumber(int a, int b)
{
    return a - b;
}
int MulTwoNumber(int a, int b)
{
    return a * b;
}

// Enum to represent operations
typedef enum
{
    ADD = 0,
    SUBTRACT,
    MULTIPLY,
    OPERATION_COUNT  // Helps define array size
} Operation;

// Typedef for a function pointer that takes two ints and returns int
typedef int (*ArithFuncPtr)(int, int);

int main()
{
    // Array of function pointers using typedef
    ArithFuncPtr arithFuncs[OPERATION_COUNT] = { AddTwoNumber, SubTwoNumber, MulTwoNumber };

    int a = 20, b = 10;

    // Call Add function
    printf("Addition = %d\n", arithFuncs[ADD](a, b));

    // Call Subtract function
    printf("Subtraction = %d\n", arithFuncs[SUBTRACT](a, b));

    // Call Multiply function
    printf("Multiplication = %d\n", arithFuncs[MULTIPLY](a, b));

    return 0;
}

Output:

 

Advantages of Using Array of Function Pointers:

  • Simplifies code for multiple operations: You can call different functions dynamically using an index instead of multiple if-else or switch statements.
  • Improves maintainability: Adding a new operation just requires adding the function to the array.
  • Useful in embedded systems: For implementing command handlers, interrupt dispatch tables, or callback mechanisms.

💡 Note: In C, you can omit the * when calling the function pointer. So arithFuncs[0](20, 10) works the same as (*arithFuncs[0])(20, 10).

 

8) Using typedef with Function Pointers in C:

Function pointers can become complex to declare, especially when you deal with arrays of pointers or functions that return function pointers. Using a typedef makes the syntax easier to read and manage.

Let’s explore how this works.

1. Typedef for an Array of Function Pointers

Suppose we have three arithmetic functions:

int AddTwoNumber(int a, int b);
int SubTwoNumber(int a, int b);
int MulTwoNumber(int a, int b);

Instead of declaring an array of function pointers manually, we can define a typedef:

// Typedef for an array of 3 function pointers

typedef int (*apfArithmatics[3])(int, int);

Here, apfArithmatics is now a type representing an array of three pointers to functions that take two int arguments and return an int.

We can then create a variable of this type and initialize it with our arithmetic functions:

apfArithmatics aArithmaticOperation = { AddTwoNumber, SubTwoNumber, MulTwoNumber };

This makes the code much cleaner and easier to maintain.

 

2. Typedef for Easier Typecasting:

Sometimes you need to typecast a void* to a function pointer. Doing this directly can look messy:

void *pvHandle = NULL;

int (*pf)(int) = (int (*)(int)) pvHandle;

Using a typedef, we can simplify this:

typedef int (*pf)(int);

pf JumpToApp = (pf)pvHandle;

Now, JumpToApp clearly indicates that it’s a function pointer, and the cast is much cleaner and readable.

 

✅ Advantages of using typedef with function pointers:

  • Improves readability and reduces clutter in complex declarations.
  • Makes arrays of function pointers much easier to define and use.
  • Simplifies typecasting from void* to function pointers.
  • Makes function pointers easier to pass around, especially in APIs or callback mechanisms.

 

9) Function Pointers in Structures:

C is not an object-oriented language, so it does not have member functions like C++. However, we can mimic object-oriented behavior using function pointers inside structures. This approach allows us to associate behavior (functions) with data (structure members), similar to how methods work in classes. It can also support a limited form of polymorphism in C.

Example,

#include <stdio.h>

struct SERVER_COM
{
    int iLenData;  // Data length

    // Function pointer to send data
    void (*pfSend)(const char *pcData, const int ciLen);

    // Function pointer to read data
    int (*pfRead)(char *pData);
};

// Sample functions to assign to the structure
void SendData(const char *pcData, const int ciLen)
{
    printf("Sending data: %.*s\n", ciLen, pcData);
}

int ReadData(char *pData)
{
    pData[0] = 'A';  // Example: read a single char
    return 1;        // Number of bytes read
}

int main()
{
    struct SERVER_COM GATEWAYCOM;

    GATEWAYCOM.iLenData = 10;
    GATEWAYCOM.pfSend = SendData;  // Assign function pointer
    GATEWAYCOM.pfRead = ReadData;  // Assign function pointer

    // Call functions via structure
    GATEWAYCOM.pfSend("Hello", 5);

    char buffer[10];
    int bytesRead = GATEWAYCOM.pfRead(buffer);
    printf("Read %d bytes: %c\n", bytesRead, buffer[0]);

    return 0;
}

Key Points:

  • Function pointers can be assigned dynamically at runtime.
  • Allows behavior to vary per instance, similar to polymorphism.
  • Keeps data and behavior together, improving code organization in large embedded projects.
  • Widely used in driver interfaces, protocol stacks, and callback implementations in C.

10) Function Pointer as a Callback Function:

In Windows Kernel-Mode Driver Framework (KMDF), callback functions are used extensively for events like Plug and Play (PnP) notifications, device preparation, and other driver operations. These callbacks are invoked by the operating system at specific events. To register a callback, a function pointer is used.

Example: Device Preparation Callback

Suppose we have a callback function MyUsbEvtDevicePrepareHardware. This function is called by the OS when the USB device needs to be prepared, such as reading descriptors and initializing hardware resources.

// Callback function definition
NTSTATUS
MyUsbEvtDevicePrepareHardware(
    _In_ WDFDEVICE Device,
    _In_ WDFCMRESLIST ResourceList,
    _In_ WDFCMRESLIST ResourceListTranslated
)
{
    // Perform hardware initialization here
    // Example: read USB descriptors, configure endpoints, etc.
    return STATUS_SUCCESS;
}

Using a Function Pointer to Register the Callback

A function pointer can be declared to match the signature of the callback:

NTSTATUS (*pfPrepareHardware)(
_In_ WDFDEVICE Device,
_In_ WDFCMRESLIST ResourceList,
_In_ WDFCMRESLIST ResourceListTranslated
);

Since the name of a function in C points to its entry address, the function pointer can be initialized using the function name:

pfPrepareHardware = MyUsbEvtDevicePrepareHardware;

Now, pfPrepareHardware can be passed or registered wherever the driver requires a callback function for device preparation. For example:

WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &callbacks);

callbacks.EvtDevicePrepareHardware = pfPrepareHardware;

This approach allows flexible registration of callback functions, making the driver modular and maintainable. You can easily change the callback logic without modifying the core registration code.

 

Advantages of Function Pointers in C:

Function pointers are one of the most powerful features in C, yet they are often underutilized or misunderstood. Here are some key advantages of using function pointers in your programs:

Dynamic Function Invocation:

A function pointer can point to any function with the same signature and invoke it whenever required. This allows flexible and dynamic execution of functions at runtime.

Passing Functions as Arguments:

Function pointers can be passed as arguments to other functions. This enables the creation of generic functions that perform operations based on user-defined behavior. A classic example is the qsort() function in C, which can sort data in ascending or descending order depending on the comparison function provided.

Runtime Flexibility:

Function pointers allow jumping between different parts of an application dynamically. This can be especially useful in modular or plugin-based architectures.

Accessing Functions in DLLs (Windows):

In Windows programming, function pointers are essential for accessing and calling functions exported from Dynamic Link Libraries (DLLs). This makes applications more modular and extensible.

Runtime Binding / Polymorphism:

Function pointers provide a form of runtime binding, which is a key aspect of polymorphism. They allow the program to decide which function to execute at runtime, rather than at compile time.

Implementing State Machines:

Function pointers are highly useful for implementing state machines in C. Each state can be represented as a function, and transitions between states can be managed dynamically through pointers.

Replacing Nested Switch-Case Statements:

Instead of using deeply nested switch-case statements, you can use an array of function pointers. This simplifies code readability and maintainability.

 

📘 Related Articles: