Skip to main content

Introduction to Function Pointers

Function pointers are variables that store the address of a function. They enable powerful programming techniques like callbacks, function dispatch tables, and plugin architectures.
Just like you can have a pointer to an integer or a string, you can have a pointer to a function. This lets you pass functions as arguments and build flexible, dynamic code.

Declaring Function Pointers

Basic Syntax

return_type (*pointer_name)(parameter_types);

Examples from the Project

From function_pointers.h:
#ifndef FUNCTIONPOINTERS
#define FUNCTIONPOINTERS
#include <stdlib.h>

void print_name(char *name, void (*f)(char *));
void array_iterator(int *array, size_t size, void(*action)(int));
int int_index(int *array, int size, int (*cmp)(int));
#endif /*FUNCTIONPOINTERS*/
Breaking down the syntax:
  • void (*f)(char *) - pointer to function taking char *, returning void
  • void(*action)(int) - pointer to function taking int, returning void
  • int (*cmp)(int) - pointer to function taking int, returning int
Reading function pointer declarations:void (*f)(char *) reads as:
  • f is a pointer (*f)
  • to a function ((*f)(...))
  • that takes a char * parameter
  • and returns void
The parentheses around *f are required because () has higher precedence than *.

Using Function Pointers as Parameters

Example 1: Print Name with Custom Formatter

From 0-print_name.c:
#include "function_pointers.h"

/**
 * print_name - prints a name
 * @name: name
 * @f: pointer to function
 * Return: void
 */
void print_name(char *name, void(*f)(char *))
{
    if (name && f)
        f(name);
}
Usage example:
void print_uppercase(char *name)
{
    for (int i = 0; name[i]; i++)
        _putchar(toupper(name[i]));
    _putchar('\n');
}

void print_lowercase(char *name)
{
    for (int i = 0; name[i]; i++)
        _putchar(tolower(name[i]));
    _putchar('\n');
}

int main(void)
{
    print_name("Bob", print_uppercase);  // Output: BOB
    print_name("Bob", print_lowercase);  // Output: bob
    return (0);
}
Key points:
  • Pass function name without parentheses: print_uppercase not print_uppercase()
  • The function pointer f is called like a regular function: f(name)
  • Always validate the function pointer before calling

Example 2: Array Iterator

From 1-array_iterator.c:
#include "function_pointers.h"

/**
 * array_iterator - function that executes a function given
 * as a parameter on each element of an array
 * @array: array to iterate over
 * @size: size of array
 * @action: pointer to function
 * Return: void
 */
void array_iterator(int *array, size_t size, void(*action)(int))
{
    size_t i;

    if (action && array)
        for (i = 0; i < size; i++)
            action(array[i]);
}
Usage example:
void print_elem(int elem)
{
    printf("%d\n", elem);
}

void print_square(int elem)
{
    printf("%d\n", elem * elem);
}

int main(void)
{
    int array[] = {1, 2, 3, 4, 5};
    
    printf("Elements:\n");
    array_iterator(array, 5, print_elem);
    
    printf("Squares:\n");
    array_iterator(array, 5, print_square);
    
    return (0);
}
Output:
Elements:
1
2
3
4
5
Squares:
1
4
9
16
25

Example 3: Search with Custom Comparison

From 2-int_index.c:
#include "function_pointers.h"

/**
 * int_index - function that searches for an integer
 * @array: array of integeres
 * @size: size of array
 * @cmp: pointer to function
 * Return: the index of the first element for which the cmp
 * function does not return 0
 * If no element matches, return -1
 * If size <= 0, return -1
 */
int int_index(int *array, int size, int (*cmp)(int))
{
    int i;

    if (size < 1 || array == NULL || cmp == NULL)
        return (-1);

    for (i = 0; i < size; i++)
    {
        if (cmp(array[i]))
            return (i);
    }

    return (-1);
}
Usage example:
int is_positive(int n)
{
    return (n > 0);
}

int is_even(int n)
{
    return (n % 2 == 0);
}

int main(void)
{
    int array[] = {-1, -2, 3, 4, 5};
    int index;
    
    index = int_index(array, 5, is_positive);
    printf("First positive at index: %d\n", index);  // Output: 2
    
    index = int_index(array, 5, is_even);
    printf("First even at index: %d\n", index);      // Output: 1
    
    return (0);
}
Always validate function pointers before calling them!
if (f)        // Good: check before calling
    f(param);

f(param);     // Bad: could segfault if f is NULL

Function Pointer Tables

Function pointer tables (dispatch tables) are powerful for implementing switch-like behavior.

Calculator Example

From 3-calc.h:
#ifndef CALC
#define CALC

#include <stdlib.h>
#include <stdio.h>
/**
 * struct op - Struct op
 *
 * @op: The operator
 * @f: The function associated
 */
typedef struct op
{
    char *op;
    int (*f)(int a, int b);
} op_t;

int op_add(int a, int b);
int op_sub(int a, int b);
int op_mul(int a, int b);
int op_div(int a, int b);
int op_mod(int a, int b);
int (*get_op_func(char *s))(int, int);

#endif /*CALC*/
The structure combines:
  • A string identifier (op)
  • A function pointer (f) to the operation

Operation Functions

From 3-op_functions.c:
#include "3-calc.h"

/**
 * op_add - adds two numbers.
 * @a: first number.
 * @b: second number.
 *
 * Return: add.
 */
int op_add(int a, int b)
{
    return (a + b);
}

/**
 * op_sub - subctracts two numbers.
 * @a: first number.
 * @b: second number.
 *
 * Return: difference.
 */
int op_sub(int a, int b)
{
    return (a - b);
}

/**
 * op_mul - multiplies two numbers.
 * @a: first number.
 * @b: second number.
 *
 * Return: multiplication.
 */
int op_mul(int a, int b)
{
    return (a * b);
}

/**
 * op_div - divides two numbers.
 * @a: first number.
 * @b: second number.
 *
 * Return: division.
 */
int op_div(int a, int b)
{
    if (b == 0)
    {
        printf("Error\n");
        exit(100);
    }
    return (a / b);
}

/**
 * op_mod - calculates the module of two numbers.
 * @a: first number.
 * @b: second number.
 *
 * Return: remainder
 */
int op_mod(int a, int b)
{
    if (b == 0)
    {
        printf("Error\n");
        exit(100);
    }
    return (a % b);
}

Function Dispatcher

From 3-get_op_func.c:
#include "3-calc.h"

/**
 * get_op_func - selects the correct function to perform
 * the operation asked by the user.
 * @s: char operator.
 *
 * Return: pointer to the function that corresponds to the operator.
 */
int (*get_op_func(char *s))(int, int)
{
    op_t ops[] = {
        {"+", op_add},
        {"-", op_sub},
        {"*", op_mul},
        {"/", op_div},
        {"%", op_mod},
        {NULL, NULL}
    };
    int i = 0;

    while (i < 10)
    {
        if (s[0] == ops->op[i])
            break;
        i++;
    }

    return (ops[i / 2].f);
}
Usage:
int main(int argc, char *argv[])
{
    int num1, num2, result;
    int (*operation)(int, int);
    
    if (argc != 4)
    {
        printf("Error\n");
        exit(98);
    }
    
    num1 = atoi(argv[1]);
    num2 = atoi(argv[3]);
    
    operation = get_op_func(argv[2]);
    
    if (operation == NULL)
    {
        printf("Error\n");
        exit(99);
    }
    
    result = operation(num1, num2);
    printf("%d\n", result);
    
    return (0);
}
Example runs:
./calc 5 + 3    # Output: 8
./calc 10 - 4   # Output: 6
./calc 6 * 7    # Output: 42
./calc 15 / 3   # Output: 5
./calc 17 % 5   # Output: 2

Function Pointer Syntax Patterns

Returning Function Pointers

// Function that returns a function pointer
int (*get_op_func(char *s))(int, int)
Reading this:
  • get_op_func(char *s) is a function taking char *
  • It returns int (*)(int, int) - a pointer to a function
  • That function takes two int parameters and returns int

Typedef for Readability

// Without typedef (complex)
int (*get_op_func(char *s))(int, int);

// With typedef (cleaner)
typedef int (*operation_t)(int, int);
operation_t get_op_func(char *s);

Common Use Cases

Callbacks

Pass functions to be called later (event handlers, async operations)

Sorting & Searching

Custom comparison functions for qsort(), bsearch()

Plugins

Load and call functions dynamically at runtime

State Machines

Function tables for state transitions

Standard Library Examples

qsort() - Sorting with Callbacks

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

int compare_ints(const void *a, const void *b)
{
    int arg1 = *(const int *)a;
    int arg2 = *(const int *)b;
    
    if (arg1 < arg2) return -1;
    if (arg1 > arg2) return 1;
    return 0;
}

int main(void)
{
    int numbers[] = {5, 2, 8, 1, 9};
    size_t count = sizeof(numbers) / sizeof(numbers[0]);
    
    qsort(numbers, count, sizeof(int), compare_ints);
    
    // numbers is now: {1, 2, 5, 8, 9}
    return (0);
}

signal() - Event Handlers

#include <signal.h>
#include <stdio.h>

void handle_interrupt(int sig)
{
    printf("\nCaught signal %d\n", sig);
}

int main(void)
{
    signal(SIGINT, handle_interrupt);
    
    while (1)
    {
        // Program continues until Ctrl+C
    }
    
    return (0);
}

Best Practices

  1. Always validate function pointers
    if (func_ptr != NULL)
        func_ptr(args);
    
  2. Use typedef for complex declarations
    typedef void (*callback_t)(int);
    callback_t my_callback;
    
  3. Document function pointer parameters
    /**
     * process_array - processes array elements
     * @array: input array
     * @size: array size
     * @process: function to apply to each element
     *           - takes int parameter (array element)
     *           - returns void
     */
    void process_array(int *array, size_t size, void (*process)(int));
    
  4. Initialize function pointers to NULL
    int (*operation)(int, int) = NULL;
    
  5. Use consistent naming conventions
    // Good: suggests it's a function pointer
    void (*on_click)(void);
    callback_t event_handler;
    
    // Less clear
    void (*f)(void);
    
Debugging tip: Function pointers can be hard to debug. Use descriptive names and add validation checks to make issues easier to track down.

Common Pitfalls

Missing Parentheses

// Wrong: declares function returning int pointer
int *func(int);

// Correct: declares pointer to function returning int
int (*func)(int);

Calling vs Passing

void my_function(int x)
{
    printf("%d\n", x);
}

// Wrong: calls the function immediately
array_iterator(arr, 5, my_function(42));

// Correct: passes the function pointer
array_iterator(arr, 5, my_function);

NULL Pointer Dereference

void (*callback)(void) = NULL;

// Wrong: will crash
callback();

// Correct: check first
if (callback)
    callback();

Key Takeaways

  • Function pointers store addresses of functions
  • Syntax: return_type (*pointer_name)(parameter_types)
  • Use for callbacks, dispatch tables, and flexible APIs
  • Always validate before calling
  • Combine with structs for powerful dispatch patterns
  • Typedef makes complex declarations readable

Structures & Typedef

Combine function pointers with structs

Variadic Functions

Functions with variable arguments

Build docs developers (and LLMs) love