Skip to content
FollowtheDamnCode

Function Pointers and Callbacks

Passing "which function to call" as a value.

8 minute read

Big idea

Functions live in memory too, so they have addresses. A function pointer stores one — letting you pass "which function to call" around like any other value.

The idea

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

int (*op)(int, int) = add;   // op points to add
printf("%d\n", op(2, 3));    // calls add: 5

Read int (*op)(int, int) from the name outwards:

  • (*op) — op is a pointer…
  • (int, int) — …to a function taking two ints…
  • int — …that returns an int.

The brackets around *op matter. Without them, int *op(int, int) declares a function that returns an int *.

A function's name on its own (no ( )) already means its address, so op = add; needs no &.

One pointer, different functions
#include <stdio.h>

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

int main(void) {
    int (*op)(int, int);

    op = add;
    printf("%d\n", op(6, 3));
    op = sub;
    printf("%d\n", op(6, 3));
    op = mul;
    printf("%d\n", op(6, 3));
    return 0;
}
output
9
3
18

Run and edit it in the app.

Watch it

Name the type with typedef

The syntax is noisy, so give the type a name:

typedef int (*BinaryOp)(int, int);

BinaryOp op = add;

Callbacks: passing a function in

The real power: a function can take another function as a parameter and call it back. The caller decides what happens; the function decides when.

apply calls whatever function you hand it
#include <stdio.h>

typedef int (*Transform)(int);

int twice(int x)  { return 2 * x; }
int square(int x) { return x * x; }

void apply(int *a, int n, Transform f) {
    for (int i = 0; i < n; i++) {
        a[i] = f(a[i]);
    }
}

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

int main(void) {
    int nums[] = {1, 2, 3, 4};
    apply(nums, 4, twice);
    print_all(nums, 4);
    apply(nums, 4, square);
    print_all(nums, 4);
    return 0;
}
output
2 4 6 8 
4 16 36 64 

Run and edit it in the app.

qsort: the standard library's callback

qsort (from <stdlib.h>) sorts any array — ints, strings, structs — because you supply the comparison:

void qsort(void *base, size_t count, size_t size,
           int (*compare)(const void *, const void *));

Your comparator gets pointers to two elements and returns:

  • negative if the first should come first,
  • 0 if they're equal,
  • positive if the second should come first.

The pointers are const void * — "a pointer to something, type unknown" — so you convert them to the real type first:

Sort ascending, then descending
#include <stdio.h>
#include <stdlib.h>

int ascending(const void *pa, const void *pb) {
    int a = *(const int *) pa;
    int b = *(const int *) pb;
    return (a > b) - (a < b);
}

int descending(const void *pa, const void *pb) {
    return ascending(pb, pa);
}

int main(void) {
    int nums[] = {42, 7, 19, 3, 25};
    int n = sizeof nums / sizeof nums[0];

    qsort(nums, n, sizeof nums[0], ascending);
    for (int i = 0; i < n; i++) printf("%d ", nums[i]);
    printf("\n");

    qsort(nums, n, sizeof nums[0], descending);
    for (int i = 0; i < n; i++) printf("%d ", nums[i]);
    printf("\n");
    return 0;
}
output
3 7 19 25 42 
42 25 19 7 3 

Run and edit it in the app.

(a > b) - (a < b) gives 1, 0, or -1. You'll often see return a - b; instead — shorter, but it can overflow for big numbers of opposite sign and sort wrongly.

Sorting structs

The same qsort, a different comparator:

Sort students by mark, highest first
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    char name[16];
    int mark;
} Student;

int by_mark_desc(const void *pa, const void *pb) {
    const Student *a = pa;
    const Student *b = pb;
    return (b->mark > a->mark) - (b->mark < a->mark);
}

int main(void) {
    Student s[] = {{"Ravi", 72}, {"Asha", 91}, {"Meera", 85}};
    qsort(s, 3, sizeof s[0], by_mark_desc);
    for (int i = 0; i < 3; i++) printf("%s %d\n", s[i].name, s[i].mark);
    return 0;
}
output
Asha 91
Meera 85
Ravi 72

Run and edit it in the app.

A table of functions

An array of function pointers turns a menu into one line of code:

Predict: which operations run?
#include <stdio.h>

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

int main(void) {
    int (*ops[])(int, int) = {add, sub, mul};
    int choices[] = {2, 0, 1};
    for (int i = 0; i < 3; i++) {
        printf("%d\n", ops[choices[i]](10, 4));
    }
    return 0;
}
What does it print? Show the answer
40
14
6

Run and edit it in the app.

A qsort comparator returns a negative number. What does that tell qsort?

Common mistake

Calling the function when you meant to pass it: qsort(nums, n, sizeof nums[0], ascending()); calls ascending with no arguments. Pass the name — ascending — and let qsort do the calling.

Think of it like

Leaving a phone number with a receptionist: "When the parcel arrives, call this number." You don't say what they'll do in between — you just tell them who to ring. That number is a callback.

Key terms

  • Function pointer — A variable holding a function's address: int (*f)(int).
  • Callback — A function passed to another function, to be called by it.
  • Comparator — A callback that says how two items should be ordered.
  • void * — A pointer to data of unknown type; convert it before use.

Check yourself

What does int (*f)(int); declare?

What should a qsort comparator return when the first element should come first?

How do you pass a comparator named cmp to qsort?