C Function Pointers

A function pointer in C is a pointer that stores the address of a function instead of the address of ordinary data. It lets a program choose which function to call at run time, pass behavior into another function, and build small dispatch tables without long chains of if statements.

Function pointers look intimidating at first because the declaration syntax wraps the name in parentheses. Once you read the type as “pointer to a function with these parameters returning this type,” they become a practical tool for callbacks, menu commands, sorting helpers, and reusable algorithms.

Overview: How Function Pointers Work

Compiled C functions live in the program’s executable code area. A normal function call such as add(2, 3) names a specific function directly. A function pointer stores a callable address, so the call can be indirect: the program reads the pointer value, jumps to that function’s code, passes the arguments, and later returns to the caller.

A function pointer has a strict type. For example, int (*operation)(int, int) means operation is a pointer to a function that takes two int arguments and returns an int. It can point to add if add has the compatible type int add(int, int). It should not point to a function that returns double, takes one argument, or expects different parameter types.

In most expressions, a function name automatically converts to a pointer to that function. Because of that, operation = add; and operation = &add; are both commonly accepted, but the shorter form is idiomatic. Likewise, a function pointer can be called with operation(2, 3). The older-looking form (*operation)(2, 3) is also valid and makes the dereference explicit.

Function pointers are not the same as pointers to objects. You cannot portably store a function pointer in an int *, do pointer arithmetic on it, or print it using ordinary object-pointer assumptions. Treat it as a callable value whose main valid operations are assignment from a compatible function, comparison with NULL, comparison with another compatible function pointer, and calling it when it is valid.

The most common use is a callback: one function receives a function pointer parameter and calls it to customize part of its work. This is how a general loop can apply different transformations, how a menu can run different commands, and how library functions such as sorting routines can ask user code how to compare two values.

Syntax

This program shows the general form: declare functions, declare a function pointer, assign a compatible function, then call through the pointer.

#include <stdio.h>

int add(int left, int right)
{
    return left + right;
}

int main(void)
{
    int (*operation)(int, int) = add;

    printf("Result: %d\n", operation(4, 5));

    return 0;
}

Output:

Result: 9
Part Meaning
int The return type of the function being pointed to.
(*operation) The parentheses make operation a pointer to a function. Without them, the meaning changes.
(int, int) The parameter types of the function being pointed to.
= add Stores the address of the compatible function add.
operation(4, 5) Calls whichever function the pointer currently names.

Read declarations from the inside out. double (*measure)(double) reads as: measure is a pointer to a function that takes one double and returns a double. When declarations get long, a typedef can give the function pointer type a clear name.

Examples

Choosing One Operation

#include <stdio.h>

int add(int left, int right)
{
    return left + right;
}

int multiply(int left, int right)
{
    return left * right;
}

int main(void)
{
    int use_multiplication = 1;
    int (*operation)(int, int);

    if (use_multiplication) {
        operation = multiply;
    } else {
        operation = add;
    }

    printf("Answer: %d\n", operation(6, 7));

    return 0;
}

Output:

Answer: 42

The variable operation can point to either add or multiply because both functions have the same signature: two int parameters and an int return value. The call site does not need to know which function was selected; it simply calls operation(6, 7).

Using A Callback Parameter

#include <stdio.h>

int square(int value)
{
    return value * value;
}

int triple(int value)
{
    return value * 3;
}

void print_transformed(const int values[], int length, int (*transform)(int))
{
    for (int i = 0; i < length; i++) {
        printf("%d ", transform(values[i]));
    }
    printf("\n");
}

int main(void)
{
    int numbers[] = { 1, 2, 3, 4 };

    print_transformed(numbers, 4, square);
    print_transformed(numbers, 4, triple);

    return 0;
}

Output:

1 4 9 16 
3 6 9 12 

The function print_transformed owns the loop, but it does not own the transformation rule. The third parameter is a function pointer named transform. Passing square produces squared output; passing triple reuses the same loop with different behavior.

Building A Dispatch Table

#include <stdio.h>

typedef int (*binary_operation)(int, int);

int add(int left, int right)
{
    return left + right;
}

int subtract(int left, int right)
{
    return left - right;
}

int multiply(int left, int right)
{
    return left * right;
}

int main(void)
{
    binary_operation operations[] = { add, subtract, multiply };
    const char *names[] = { "add", "subtract", "multiply" };
    int left = 8;
    int right = 3;

    for (int i = 0; i < 3; i++) {
        printf("%s: %d\n", names[i], operations[i](left, right));
    }

    return 0;
}

Output:

add: 11
subtract: 5
multiply: 24

The typedef names the function pointer type binary_operation, which makes the array declaration readable. Each array element points to a function with the same signature. This pattern is called a dispatch table because an index chooses which function to dispatch to.

Checking For A Missing Callback

#include <stdio.h>

void run_if_available(void (*action)(void))
{
    if (action == NULL) {
        printf("No action\n");
        return;
    }

    action();
}

void print_ready(void)
{
    printf("Ready\n");
}

int main(void)
{
    run_if_available(print_ready);
    run_if_available(NULL);

    return 0;
}

Output:

Ready
No action

A function pointer can be NULL when no callback is available. The function run_if_available checks before calling. Calling through a null function pointer is undefined behavior, so this guard is essential when NULL is part of the interface.

How It Works Step By Step

  1. The compiler sees each function definition and knows the function’s address and type.
  2. The declaration int (*operation)(int, int) creates a variable capable of storing the address of a compatible function.
  3. The assignment operation = multiply; stores the address of multiply in that variable.
  4. When the program evaluates operation(6, 7), it evaluates the arguments, follows the function pointer to the selected code, and calls that function.
  5. The selected function receives its own parameters, returns a value, and control resumes after the indirect call.

The compiler still checks the static type of the function pointer. That is why using accurate declarations matters: the call must pass the number and kinds of arguments expected by the pointer type, and the caller will interpret the returned value according to that type. Forcing an incompatible function into a pointer with a cast may silence a warning, but it does not make the call safe.

Common Mistakes

Forgetting The Parentheses In The Declaration

The declaration int *operation(int, int) does not declare a function pointer variable. It declares a function named operation that returns int *. The corrected declaration wraps the name and asterisk in parentheses:

#include <stdio.h>

int add(int left, int right)
{
    return left + right;
}

int main(void)
{
    int (*operation)(int, int) = add;

    printf("Sum: %d\n", operation(10, 15));

    return 0;
}

Output:

Sum: 25

The parentheses are not decorative. They control how C parses the declaration, so they are the difference between a pointer variable and a function declaration.

Using An Incompatible Function Signature

A pointer to a function taking two int arguments should only point to functions with a compatible signature. Do not assign a one-parameter function or a different return type and hope the call works. The corrected version gives every selectable function the same type:

#include <stdio.h>

int minimum(int left, int right)
{
    return left < right ? left : right;
}

int maximum(int left, int right)
{
    return left > right ? left : right;
}

int main(void)
{
    int (*choose)(int, int) = maximum;

    printf("Chosen: %d\n", choose(12, 20));
    choose = minimum;
    printf("Chosen: %d\n", choose(12, 20));

    return 0;
}

Output:

Chosen: 20
Chosen: 12

Both minimum and maximum match int (*)(int, int), so the compiler can check calls correctly. If you need different signatures, write separate pointer types or adapter functions.

Calling Before Initialization

A function pointer variable is not automatically useful after declaration. If it has not been initialized, calling through it has undefined behavior. Initialize it when possible, or set it to NULL and check before calling, as shown in the missing-callback example.

Best Practices

  • Keep the function pointer type exact. The return type and parameter types must match the functions you assign.
  • Use a typedef for repeated or long function pointer types, especially in arrays and callback-heavy code.
  • Initialize function pointers before use. Use NULL when “no function selected” is a real state.
  • Check for NULL before calling when an interface allows a missing callback.
  • Prefer clear callback parameter names such as compare, transform, or action.
  • Avoid casts between incompatible function pointer types. A cast can hide a compiler warning while leaving undefined behavior.
  • Use dispatch tables for small, fixed sets of operations; use ordinary if or switch logic when it is clearer.

Practice Exercises

  1. Write two functions, is_even and is_positive, that return 1 or 0. Write a function that receives int (*test)(int) and counts how many array elements pass the test.
  2. Create a menu with three command functions that take no parameters and return void. Store them in an array of function pointers and call the command at a chosen index.
  3. Write a typedef for a function pointer named double_transform that points to a function taking one double and returning a double. Use it to apply two different transformations.

Summary

  • A function pointer stores the address of a callable function.
  • The core syntax is return_type (*name)(parameter_types).
  • A function name usually converts to a pointer to that function when assigned or passed.
  • Calling through a function pointer performs an indirect call to the selected function.
  • Callbacks let one function receive behavior from another part of the program.
  • Arrays of function pointers can act as dispatch tables for related operations.
  • Only call initialized, non-NULL function pointers with compatible signatures.