Lesson 157 min read

Function Pointers & Callbacks

Imagine a remote control where each button can be reprogrammed to do anything β€” that's a function pointer.

The Programmable Remote Control

In most C code, you call functions by name: printf(), strlen(), main(). But what if you could store a function in a variable, pass it to another function, or pick which function to call at runtime?

That's what function pointers do. Just like a regular pointer holds the address of a variable, a function pointer holds the address of a function. It's like a remote control where each button stores the address of a different action β€” and you can reprogram the buttons.

The Syntax (Yes, It's Ugly)

A function pointer declaration looks like this:

return_type (*pointer_name)(param_types);

The parentheses around *pointer_name are critical. Without them, you'd be declaring a function that returns a pointer β€” a very different thing.

Basic Function Pointer

#include <stdio.h>
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int main() {
// Declare a function pointer
int (*operation)(int, int);
// Point it to different functions
operation = add;
printf("add(3, 4) = %d\n", operation(3, 4));
operation = subtract;
printf("subtract(3, 4) = %d\n", operation(3, 4));
operation = multiply;
printf("multiply(3, 4) = %d\n", operation(3, 4));
return 0;
}
Output
add(3, 4)      = 7
subtract(3, 4) = -1
multiply(3, 4) = 12

typedef to the Rescue

The raw syntax for function pointers is notoriously hard to read. typedef lets you create a clean, readable name for the function pointer type. Once you define the type, you can declare variables, function parameters, and arrays with it β€” just like any other type.

typedef for Function Pointers

#include <stdio.h>
// Define a type for "function that takes two ints, returns int"
typedef int (*MathOp)(int, int);
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
// Now function pointers as parameters are clean!
void compute(MathOp op, int x, int y, const char *label) {
printf("%s(%d, %d) = %d\n", label, x, y, op(x, y));
}
int main() {
compute(add, 10, 3, "add");
compute(subtract, 10, 3, "subtract");
return 0;
}
Output
add(10, 3) = 13
subtract(10, 3) = 7
Note: The syntax for function pointers is notoriously ugly. typedef makes it readable. Always use typedef for function pointer types β€” your future self (and everyone on your team) will thank you.

Callbacks β€” Passing Functions as Arguments

A callback is a function you pass to another function, saying: "Call this when you need to." It's one of the most powerful patterns in C. The standard library uses it everywhere β€” qsort, bsearch, signal handlers, and more.

Think of it like hiring a contractor and handing them a phone number: "When the job is done, call this number." The contractor doesn't know who they're calling β€” they just dial the number you gave them.

Callback Pattern

#include <stdio.h>
typedef int (*Predicate)(int);
int is_even(int n) { return n % 2 == 0; }
int is_positive(int n) { return n > 0; }
int is_large(int n) { return n > 50; }
// Generic filter: counts how many items pass a test
int count_matching(int arr[], int size, Predicate test) {
int count = 0;
for (int i = 0; i < size; i++) {
if (test(arr[i])) count++;
}
return count;
}
int main() {
int data[] = {-3, 12, 7, 84, -15, 50, 63, 2};
int n = 8;
printf("Even numbers: %d\n", count_matching(data, n, is_even));
printf("Positive numbers: %d\n", count_matching(data, n, is_positive));
printf("Large numbers: %d\n", count_matching(data, n, is_large));
return 0;
}
Output
Even numbers:     4
Positive numbers: 5
Large numbers:    2

Real-World Example: qsort

The C standard library's qsort function sorts any array β€” integers, strings, structs, anything. How? It takes a comparator callback that tells it how to compare two elements. You provide the logic; qsort provides the sorting algorithm.

The comparator must return:

  • Negative if the first element should come before the second
  • Zero if they're equal
  • Positive if the first should come after the second

Using qsort with a Custom Comparator

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Comparator for ascending order
int compare_asc(const void *a, const void *b) {
return (*(int *)a - *(int *)b);
}
// Comparator for descending order
int compare_desc(const void *a, const void *b) {
return (*(int *)b - *(int *)a);
}
void print_array(int arr[], int n, const char *label) {
printf("%s: ", label);
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
}
int main() {
int nums[] = {42, 17, 93, 8, 55, 31};
int n = 6;
print_array(nums, n, "Original ");
qsort(nums, n, sizeof(int), compare_asc);
print_array(nums, n, "Ascending ");
qsort(nums, n, sizeof(int), compare_desc);
print_array(nums, n, "Descending");
return 0;
}
Output
Original  : 42 17 93 8 55 31
Ascending : 8 17 31 42 55 93
Descending: 93 55 42 31 17 8

Dispatch Table β€” Array of Function Pointers

An array of function pointers is called a dispatch table. Instead of a long switch statement, you index into an array and call the function at that position. This pattern is used in interpreters, command processors, and state machines.

Simple Dispatch Table (Calculator)

#include <stdio.h>
typedef double (*MathFunc)(double, double);
double op_add(double a, double b) { return a + b; }
double op_sub(double a, double b) { return a - b; }
double op_mul(double a, double b) { return a * b; }
double op_div(double a, double b) { return b != 0 ? a / b : 0; }
int main() {
// Dispatch table: array of function pointers
MathFunc operations[] = { op_add, op_sub, op_mul, op_div };
const char *symbols[] = { "+", "-", "*", "/" };
double a = 20.0, b = 6.0;
for (int i = 0; i < 4; i++) {
double result = operations[i](a, b);
printf("%.0f %s %.0f = %.2f\n", a, symbols[i], b, result);
}
return 0;
}
Output
20 + 6 = 26.00
20 - 6 = 14.00
20 * 6 = 120.00
20 / 6 = 3.33
Note: Function pointer syntax cheat sheet: Without typedef: int (*fp)(int, int) = add; With typedef: typedef int (*MathOp)(int, int); MathOp fp = add; Both are identical β€” typedef just gives the type a name. When in doubt, use typedef.
Challenge

Quick check

What does a function pointer store?
← Preprocessor & Macros