Function Pointers
You know what pointers are. Variables that store addresses. Usually addresses of data - integers, arrays, structs.
But functions live in memory too. They have addresses. And you can store those addresses in variables.
A function pointer is a variable that holds the address of a function. Instead of calling the function directly, you call it through the pointer.
Why would you want this? Because it lets you pass behavior around like data. “Here’s the data, and here’s what to do with it.”
The Phone Number Analogy
Think of a function pointer like storing someone’s phone number.
You don’t have the actual person in your phone. You have their number - a way to reach them when you need them. You can give that number to someone else. They can call it without knowing who they’re calling.
A function pointer is the same. You don’t have the function itself. You have its address - a way to call it when you need it. You can pass that address to other code. That code can call the function without knowing which function it’s calling.
Your First Function Pointer
Let’s start simple. Here’s a function:
int add(int a, int b) {
return a + b;
}And here’s a pointer that can hold its address:
int (*ptr)(int, int);That syntax looks strange. Let’s break it down:
ptr- the name of our pointer variable*ptr- the*says this is a pointer(*ptr)(int, int)- the parentheses after say “this points to a function that takes two ints”int (*ptr)(int, int)- theintat the front says “that function returns an int”
Now let’s use it:
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main(void) {
int (*ptr)(int, int); // Declare the pointer
ptr = add; // Store the function's address
int result = ptr(3, 5); // Call through the pointer
printf("%d\n", result); // 8
return 0;
}Notice: we wrote ptr = add, not ptr = add(). Adding parentheses would call the function and try to store its return value. We want the address of the function itself.
Also notice: add without parentheses is automatically treated as a pointer. You can also write ptr = &add - both mean the same thing.
Why the Weird Syntax?
C’s declaration syntax follows the rule “declaration follows use.” The declaration int (*ptr)(int, int) tells you that if you write (*ptr)(3, 5), you get an int.
In practice, you don’t need the * when calling. These are equivalent:
int result = (*ptr)(3, 5); // Explicit dereference
int result = ptr(3, 5); // Also works - C does it for youMost people use the second form. It’s cleaner.
Simplifying with typedef
That declaration syntax is ugly. Use typedef to make it readable:
typedef int (*MathFunc)(int, int);
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(void) {
MathFunc operation; // Much cleaner!
operation = add;
printf("3 + 5 = %d\n", operation(3, 5));
operation = subtract;
printf("3 - 5 = %d\n", operation(3, 5));
operation = multiply;
printf("3 * 5 = %d\n", operation(3, 5));
return 0;
}Now MathFunc is a type meaning “pointer to a function that takes two ints and returns an int.”
Passing Functions as Arguments
Here’s where function pointers get powerful. You can pass a function to another function:
#include <stdio.h>
typedef int (*MathFunc)(int, int);
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
void calculate_and_print(int x, int y, MathFunc operation) {
int result = operation(x, y);
printf("Result: %d\n", result);
}
int main(void) {
calculate_and_print(10, 3, add); // Result: 13
calculate_and_print(10, 3, subtract); // Result: 7
return 0;
}The function calculate_and_print doesn’t know or care whether it’s adding or subtracting. It just calls whatever function it’s given.
This is like hiring a contractor and telling them “here’s the job, use whatever tool works best.” The contractor (your code) doesn’t need to know the specifics - it just uses what you give it.
The Real Power: qsort
The C standard library has a function called qsort that sorts arrays. But how does it know how to compare elements? You could sort numbers, strings, structs - it can’t know in advance.
The answer: you tell it. You pass a comparison function.
#include <stdio.h>
#include <stdlib.h>
int compare_ints(const void *a, const void *b) {
int int_a = *(const int *)a;
int int_b = *(const int *)b;
if (int_a < int_b) return -1;
if (int_a > int_b) return 1;
return 0;
}
int main(void) {
int numbers[] = {42, 17, 8, 99, 3, 25};
int count = sizeof(numbers) / sizeof(numbers[0]);
printf("Before: ");
for (int i = 0; i < count; i++) printf("%d ", numbers[i]);
printf("\n");
qsort(numbers, count, sizeof(int), compare_ints);
printf("After: ");
for (int i = 0; i < count; i++) printf("%d ", numbers[i]);
printf("\n");
return 0;
}Output:
Before: 42 17 8 99 3 25
After: 3 8 17 25 42 99The qsort function signature is:
void qsort(void *base, size_t count, size_t size,
int (*compare)(const void *, const void *));That last parameter is a function pointer. qsort calls your comparison function whenever it needs to compare two elements.
Why void pointers?
The comparison function uses const void * because qsort is generic - it works with any type. Inside your comparison function, you cast to the actual type you’re sorting.
Sorting in Reverse
Want to sort from largest to smallest? Just flip the comparison:
int compare_reverse(const void *a, const void *b) {
int int_a = *(const int *)a;
int int_b = *(const int *)b;
if (int_a < int_b) return 1; // Flipped!
if (int_a > int_b) return -1; // Flipped!
return 0;
}Same sorting algorithm, different behavior - just by passing a different function.
Sorting Strings
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int compare_strings(const void *a, const void *b) {
// For string arrays, we get pointers to the string pointers
const char *str_a = *(const char **)a;
const char *str_b = *(const char **)b;
return strcmp(str_a, str_b);
}
int main(void) {
const char *names[] = {"Charlie", "Alice", "Bob", "Diana"};
int count = sizeof(names) / sizeof(names[0]);
qsort(names, count, sizeof(char *), compare_strings);
for (int i = 0; i < count; i++) {
printf("%s\n", names[i]);
}
return 0;
}Output:
Alice
Bob
Charlie
DianaSorting Structs
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char name[50];
int score;
} Player;
int compare_by_score(const void *a, const void *b) {
const Player *pa = (const Player *)a;
const Player *pb = (const Player *)b;
return pb->score - pa->score; // Descending order
}
int compare_by_name(const void *a, const void *b) {
const Player *pa = (const Player *)a;
const Player *pb = (const Player *)b;
return strcmp(pa->name, pb->name);
}
int main(void) {
Player players[] = {
{"Alice", 850},
{"Bob", 920},
{"Charlie", 780}
};
int count = sizeof(players) / sizeof(players[0]);
printf("By score (highest first):\n");
qsort(players, count, sizeof(Player), compare_by_score);
for (int i = 0; i < count; i++) {
printf(" %s: %d\n", players[i].name, players[i].score);
}
printf("\nBy name:\n");
qsort(players, count, sizeof(Player), compare_by_name);
for (int i = 0; i < count; i++) {
printf(" %s: %d\n", players[i].name, players[i].score);
}
return 0;
}Callbacks
A callback is a function you give to someone else’s code so they can “call you back” when something happens.
Think of it like leaving your phone number with a restaurant. “Call me when my table is ready.” You don’t wait around - you give them a way to reach you, and they use it when the time is right.
Here’s a simple example - a function that processes items and calls back for each one:
#include <stdio.h>
typedef void (*ItemCallback)(int item, int index);
void for_each(int arr[], int size, ItemCallback callback) {
for (int i = 0; i < size; i++) {
callback(arr[i], i);
}
}
void print_item(int item, int index) {
printf("Item %d: %d\n", index, item);
}
void print_doubled(int item, int index) {
printf("Item %d doubled: %d\n", index, item * 2);
}
int main(void) {
int numbers[] = {10, 20, 30, 40, 50};
int size = sizeof(numbers) / sizeof(numbers[0]);
printf("Original values:\n");
for_each(numbers, size, print_item);
printf("\nDoubled:\n");
for_each(numbers, size, print_doubled);
return 0;
}The for_each function doesn’t know what to do with each item. It just walks through the array and calls your function for each one. You decide the behavior.
Event Handling
Callbacks are everywhere in event-driven programming. Here’s a simple example:
#include <stdio.h>
typedef void (*EventHandler)(const char *event);
void on_button_click(const char *event) {
printf("Button was clicked! Event: %s\n", event);
}
void on_key_press(const char *event) {
printf("Key was pressed! Event: %s\n", event);
}
void simulate_event(const char *event, EventHandler handler) {
printf("Event occurred: %s\n", event);
if (handler != NULL) {
handler(event);
}
}
int main(void) {
simulate_event("CLICK", on_button_click);
simulate_event("KEYDOWN", on_key_press);
return 0;
}Real GUI libraries work exactly like this - you register callback functions for events, and the library calls them when those events happen.
Arrays of Function Pointers
You can create arrays of function pointers. This is useful for menu systems, command dispatchers, and state machines.
Menu System Example
#include <stdio.h>
void option_new(void) {
printf("Creating new file...\n");
}
void option_open(void) {
printf("Opening file...\n");
}
void option_save(void) {
printf("Saving file...\n");
}
void option_quit(void) {
printf("Goodbye!\n");
}
int main(void) {
// Array of function pointers
void (*menu[])(void) = {option_new, option_open, option_save, option_quit};
const char *labels[] = {"New", "Open", "Save", "Quit"};
int menu_size = sizeof(menu) / sizeof(menu[0]);
int choice;
do {
printf("\n=== MENU ===\n");
for (int i = 0; i < menu_size; i++) {
printf("%d. %s\n", i + 1, labels[i]);
}
printf("Choice: ");
scanf("%d", &choice);
if (choice >= 1 && choice <= menu_size) {
menu[choice - 1](); // Call the function at that index
}
} while (choice != menu_size); // Quit is last option
return 0;
}Instead of a big switch statement, we just index into the array and call. Adding a new menu option means adding one function and one entry in the array.
Calculator with Function Array
#include <stdio.h>
typedef int (*Operation)(int, int);
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 divide(int a, int b) { return b != 0 ? a / b : 0; }
int main(void) {
Operation ops[] = {add, subtract, multiply, divide};
char symbols[] = {'+', '-', '*', '/'};
int a = 20, b = 4;
for (int i = 0; i < 4; i++) {
printf("%d %c %d = %d\n", a, symbols[i], b, ops[i](a, b));
}
return 0;
}Output:
20 + 4 = 24
20 - 4 = 16
20 * 4 = 80
20 / 4 = 5Returning Function Pointers
Functions can return function pointers too:
#include <stdio.h>
typedef int (*Operation)(int, int);
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
Operation get_operation(char op) {
if (op == '+') return add;
if (op == '-') return subtract;
return NULL;
}
int main(void) {
Operation op = get_operation('+');
if (op != NULL) {
printf("10 + 5 = %d\n", op(10, 5));
}
op = get_operation('-');
if (op != NULL) {
printf("10 - 5 = %d\n", op(10, 5));
}
return 0;
}This pattern is useful for factory functions that return different behaviors based on configuration.
Function Pointers in Structs
You can put function pointers in structs. This is the foundation of how C programs simulate object-oriented patterns:
#include <stdio.h>
#include <string.h>
typedef struct {
char name[50];
int health;
void (*attack)(const char *target);
void (*heal)(int *health);
} Character;
void warrior_attack(const char *target) {
printf("Swings sword at %s!\n", target);
}
void warrior_heal(int *health) {
*health += 10;
printf("Bandages wounds. Health now: %d\n", *health);
}
void mage_attack(const char *target) {
printf("Casts fireball at %s!\n", target);
}
void mage_heal(int *health) {
*health += 25;
printf("Casts healing spell. Health now: %d\n", *health);
}
int main(void) {
Character warrior = {"Conan", 100, warrior_attack, warrior_heal};
Character mage = {"Gandalf", 80, mage_attack, mage_heal};
printf("%s (HP: %d):\n", warrior.name, warrior.health);
warrior.attack("goblin");
warrior.heal(&warrior.health);
printf("\n%s (HP: %d):\n", mage.name, mage.health);
mage.attack("dragon");
mage.heal(&mage.health);
return 0;
}Different characters, same interface, different behaviors. This is polymorphism - the same operation doing different things depending on who’s doing it.
Try It Yourself
Write a function
apply_to_arraythat takes an array, its size, and a function pointer. It should call the function on each element and store the result back. Test it with functions that double, square, and negate values.Extend the
qsortexamples to sort an array of structs containing student names and grades. Write comparison functions to sort by name and by grade.Create a simple command-line calculator that uses an array of function pointers. Support +, -, *, /, and % operations.
Write a
filterfunction that takes an array and a predicate function (a function that returns 1 for items to keep, 0 for items to discard). Return a new array containing only the matching items.Create a “plugin system” where you have an array of function pointers that all get called in sequence. Add functions to register and unregister plugins.
Common Mistakes
Calling instead of pointing: Writing
ptr = add()instead ofptr = add. The parentheses call the function - you want its address.Wrong signature: The function pointer type must exactly match the function.
int (*ptr)(int)cannot point to a function that takes two ints.Forgetting NULL checks: A function pointer can be NULL. Calling a NULL function pointer crashes your program. Always check if the pointer is valid before calling.
Confusing declaration syntax: Remember,
int *func(int)is a function returning a pointer.int (*func)(int)is a pointer to a function. The parentheses around*funcmake all the difference.Not using typedef: The raw syntax is hard to read. Use typedef to create meaningful type names for your function pointer types.
Next Up
In Part 16, we’ll build our first real data structure: linked lists. You’ll see how structs with pointers can create chains of data that grow and shrink easily.
Enjoyed This?
If this helped something click, subscribe to my YouTube channel. More content like this, same approach - making things stick without insulting your intelligence. It’s free, it helps more people find this stuff, and it tells me what’s worth making more of.