advanced-c Lesson 6 22 min read

What Are Function Pointers and How Do I Use Them?

Storing and calling functions by pointer — C's version of callbacks and interfaces

Reading: C Text: Ch. 13 §3 (pp. 750–765, Function Pointers in Context)

After this lesson, you will be able to:

  • Declare function pointer variables with correct syntax
  • Use typedef to create named type aliases for function pointer signatures
  • Write a sort function that accepts a comparator function pointer
  • Write qsort-compatible comparison functions

The Motivation

Look at your sort-by-name and sort-by-GPA functions. They’re identical except for one line — the comparison. Wouldn’t it be great to write one sort function and tell it how to compare?

That’s what function pointers do. You store a function’s address in a variable and call it later — just like you store a variable’s address in an int *. This enables callbacks, generic algorithms, and the kind of polymorphism that Java achieves with interfaces.


How Function Pointers Work

Functions Have Addresses

Just like variables, functions live at memory addresses. The function’s name (without parentheses) gives you that address:

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

printf("add lives at: %p\n", (void *)add);
printf("sub lives at: %p\n", (void *)sub);

Declaring Function Pointers

int (*op)(int, int);     // op is a pointer to a function taking two ints, returning int

The parentheses around (*op) are critical:

  • int (*op)(int, int) — pointer to a function
  • int *op(int, int) — function returning int * (completely different!)

Assigning and Calling

int (*op)(int, int) = add;     // Point to add function
printf("%d\n", op(3, 5));      // Calls add(3, 5) → 8

op = sub;                       // Now point to sub
printf("%d\n", op(3, 5));      // Calls sub(3, 5) → -2
Check Your Understanding
What is the difference between int (*op)(int, int) and int *op(int, int)?
A The first is a pointer to a function returning int; the second is a function returning int *
B The first declares a function; the second declares a pointer
C They're equivalent — the parentheses are optional
D The first is invalid syntax; only the second compiles
Answer: A. The parentheses around (*op) are critical. Without them, the * binds to the return type, making it a function declaration that returns int *. With them, op is a pointer to a function. This is one of C's most confusing syntax rules.

Using typedef for Readability

The raw syntax is ugly. typedef helps:

typedef int (*MathOp)(int, int);

MathOp op = add;
printf("%d\n", op(3, 5));    // 8

The Real Payoff: One Sort, Any Comparison

typedef int (*Comparator)(const Student *, const Student *);

void sort_roster(Student roster[], int size, Comparator cmp)
{
    for (int i = 0; i < size - 1; i++)
    {
        int min = i;
        for (int j = i + 1; j < size; j++)
        {
            if (cmp(&roster[j], &roster[min]) < 0)
            {
                min = j;
            }
        }
        if (min != i)
        {
            Student temp = roster[i];
            roster[i] = roster[min];
            roster[min] = temp;
        }
    }
}

Now write comparison functions:

int compare_by_name(const Student *a, const Student *b)
{
    return strcmp(a->name, b->name);
}

int compare_by_gpa(const Student *a, const Student *b)
{
    if (a->gpa < b->gpa) return -1;
    if (a->gpa > b->gpa) return 1;
    return 0;
}

Call with any comparison:

sort_roster(roster, count, compare_by_name);    // Sort alphabetically
sort_roster(roster, count, compare_by_gpa);     // Sort by GPA

Key Insight: The sort function doesn’t know or care what field it’s sorting by. It delegates the comparison to whatever function you pass. This is the same concept as Java’s Comparator interface — except in C, it’s a raw function pointer instead of an object.

Check Your Understanding
Given sort_roster(roster, count, compare_by_gpa);, what does compare_by_gpa (without parentheses) evaluate to?
A The return value of calling compare_by_gpa with no arguments
B A copy of the function's code
C The memory address of the compare_by_gpa function
D The string "compare_by_gpa"
Answer: C. A function name without parentheses gives you the function's address, just like an array name gives you the array's address. You're passing a pointer to the function, not calling it. The sort function stores this address and calls the function later through the pointer.
Why does this matter?

Function pointers are how C achieves polymorphism — calling different behavior through the same interface. They’re used in qsort, signal handlers, callback-driven libraries, and the generic container pattern in Lab 8. If you understand function pointers, you understand the mechanism behind Java interfaces.

qsort: The Standard Library’s Version

C’s standard library includes qsort, which uses function pointers for the same pattern:

#include <stdlib.h>

int compare_ints(const void *a, const void *b)
{
    return *(const int *)a - *(const int *)b;
}

int arr[] = {5, 2, 8, 1, 4};
qsort(arr, 5, sizeof(int), compare_ints);

Common Pitfall: For integer comparisons in qsort, return a - b can overflow if a and b have very different magnitudes. Safer to use explicit if statements: if (a < b) return -1; if (a > b) return 1; return 0;

From Java: Function pointers are C’s equivalent of Java interfaces and lambdas. Where Java uses Comparator<Student> and (a, b) -> a.getName().compareTo(b.getName()), C uses int (*cmp)(const Student *, const Student *) and a named comparison function. Same concept, different syntax.

Quick Check: What do the parentheses in int (*op)(int, int) do?

They group *op to indicate “op is a pointer to a function.” Without them, int *op(int, int) declares a function named op that returns int * — completely different meaning.

Quick Check: How does the sort function call the right comparison without knowing the field?

The sort function calls cmp(&roster[j], &roster[min]) — it doesn’t know what cmp does internally. The caller chooses the comparison function (compare_by_name, compare_by_gpa, etc.) and passes it as an argument. The sort function just calls whatever it receives.

Quick Check: Why is return a - b risky in comparison functions?

Integer overflow. If a is INT_MAX and b is -1, then a - b overflows and produces a negative number — the wrong result. Use explicit comparisons instead.


Big Picture: Function pointers turn functions into data — you can store them, pass them around, and call them later. This is the foundation of callbacks, plugin architectures, and event-driven programming. Every time you pass a comparator to a sort function, register a signal handler, or set up a callback in a GUI framework, you’re using this concept.


What’s Next

Function pointers let you parameterize behavior. But the sort function above only works with Student structs. What if you want one sort function that works with any type? That’s where void pointers come in — C’s mechanism for type erasure and generic programming.