Function Pointers in C
Updated 9 min read Aimerscrew
So far, your pointers have pointed at data: numbers, arrays and other pointers. Function pointers in C point at something different: code. A function pointer stores the address of a function, which means you can keep a function in a variable, pass it to another function, or pick which one to run while the program is running. In this lesson, you'll declare and call function pointers, build a menu with an array of them, and sort data with the standard library's qsort.
🌍 Real-life example
Think of a TV remote. Each button is wired to an action: one turns the power on, one raises the volume, one switches to the next channel. The button doesn't contain the action; it just knows where the action lives. Some universal remotes even let you reprogram a button to do something else. A function pointer is that button: it stores where a function lives, and pressing it (calling it) runs that function.
What is a function pointer in C?
When your program is compiled, every function's machine code is placed somewhere in memory, so every function has an address, just like a variable. A function pointer is a variable that holds one of those addresses. Calling through the pointer runs the function it points to.
In C, a function's name on its own (without parentheses) already means "the address of this function". So add and &add give the same thing.
How do you declare a function pointer?
The declaration copies the function's signature, with (*name) in place of the function name:
int add(int a, int b); /* a function */
int (*op)(int, int); /* a pointer to any function like it */
Read it from the name outward: op is a pointer (*op) to a function that takes (int, int) and returns int. The parentheses around *op are essential:
| Declaration | Meaning |
|---|---|
int (*op)(int, int); | op is a pointer to a function returning int |
int *op(int, int); | op is a function that returns an int * |
A function pointer can only point to functions with the same parameter types and return type. That's how the compiler checks your calls.
Assigning and calling a function pointer
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
int main(void) {
int (*op)(int, int);
op = add;
printf("add: %d\n", op(6, 3));
op = multiply;
printf("multiply: %d\n", op(6, 3));
op = &add; /* the & is optional */
printf("again: %d\n", (*op)(6, 3));
return 0;
}
add: 9
multiply: 18
again: 9
The same line op(6, 3) ran two different functions, depending on what op held at that moment. You can call with op(6, 3) or the older style (*op)(6, 3); both do the same thing.
Passing a function to another function (callbacks)
A callback is a function you hand to another function so that it can "call you back" at the right moment. The receiving function doesn't need to know which function it got; it just calls it.
#include <stdio.h>
double half_price(double price) {
return price / 2;
}
double ten_off(double price) {
return price - 10;
}
void show_price(const char *label, double price, double (*rule)(double)) {
printf("%s: $%.2f\n", label, rule(price));
}
int main(void) {
double price = 80.0;
show_price("Half price", price, half_price);
show_price("Ten dollars off", price, ten_off);
return 0;
}
Half price: $40.00
Ten dollars off: $70.00
show_price handles the printing; the discount rule is plugged in from outside. Adding a new rule later means writing one small function, with no changes to show_price.
Arrays of function pointers: a calculator menu
Because function pointers are values, you can store them in an array, just like remote buttons in a row. Instead of a long switch, the user's choice becomes an index into the array.
int (*ops[4])(int, int) = {add, subtract, multiply, divide};
Read it as: ops is an array of 4 pointers to functions taking (int, int) and returning int.
ops stores the address of a function. Calling ops[2](6, 3) follows the pointer to multiply. (Addresses are made-up examples.)#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 divide(int a, int b) { return a / b; }
int main(void) {
int (*ops[4])(int, int) = {add, subtract, multiply, divide};
const char *names[4] = {"Add", "Subtract", "Multiply", "Divide"};
int choice;
for (int i = 0; i < 4; i++) {
printf("%d. %s\n", i + 1, names[i]);
}
printf("Choose an operation: ");
if (scanf("%d", &choice) != 1 || choice < 1 || choice > 4) {
printf("Invalid choice\n");
return 1;
}
printf("20 and 4 gives %d\n", ops[choice - 1](20, 4));
return 0;
}
Here's a sample run where the user typed 3:
1. Add
2. Subtract
3. Multiply
4. Divide
Choose an operation: 3
20 and 4 gives 80
Notice the check before using the choice. An index outside 0 to 3 would read a garbage "address" and calling it would crash the program, so always validate the index first.
Sorting with qsort and a comparator
The most famous use of callbacks in C is qsort from <stdlib.h>. It can sort an array of any type, because you give it a comparator: a function that compares two elements and returns a negative number, zero or a positive number.
void qsort(void *base, size_t count, size_t size,
int (*compare)(const void *, const void *));
qsort passes your comparator two const void * pointers. A void * is a "pointer to anything", so you first convert it to the real type and then read the value.
#include <stdio.h>
#include <stdlib.h>
int ascending(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
return (x > y) - (x < y);
}
int descending(const void *a, const void *b) {
return ascending(b, a);
}
void print_scores(const char *label, const int scores[], size_t count) {
printf("%s:", label);
for (size_t i = 0; i < count; i++) {
printf(" %d", scores[i]);
}
printf("\n");
}
int main(void) {
int scores[] = {72, 95, 58, 88, 64};
size_t count = sizeof scores / sizeof scores[0];
qsort(scores, count, sizeof scores[0], ascending);
print_scores("Low to high", scores, count);
qsort(scores, count, sizeof scores[0], descending);
print_scores("High to low", scores, count);
return 0;
}
Low to high: 58 64 72 88 95
High to low: 95 88 72 64 58
(x > y) - (x < y) gives 1, 0 or -1. It's safer than return x - y;, which can overflow with very large or very negative numbers.
Sorting strings with qsort
For an array of strings, each element is a const char *. So the comparator receives a pointer to that pointer, a double pointer, and hands the two strings to strcmp:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int by_name(const void *a, const void *b) {
const char *first = *(const char *const *)a;
const char *second = *(const char *const *)b;
return strcmp(first, second);
}
int main(void) {
const char *cities[] = {"Tokyo", "Berlin", "Lima", "Cairo", "Oslo"};
size_t count = sizeof cities / sizeof cities[0];
qsort(cities, count, sizeof cities[0], by_name);
for (size_t i = 0; i < count; i++) {
printf("%s\n", cities[i]);
}
return 0;
}
Berlin
Cairo
Lima
Oslo
Tokyo
Only the pointers were rearranged; the city names themselves stayed where they were in memory.
Making function pointers readable with typedef
Function pointer syntax gets hard to read quickly. The keyword typedef gives a type a new, shorter name. You'll learn it fully in typedef in C, but here's a preview:
#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 calculate(Operation op, int a, int b) {
return op(a, b);
}
int main(void) {
Operation ops[3] = {add, subtract, multiply};
const char *names[3] = {"Add", "Subtract", "Multiply"};
for (int i = 0; i < 3; i++) {
printf("%s: %d\n", names[i], calculate(ops[i], 12, 5));
}
return 0;
}
Add: 17
Subtract: 7
Multiply: 60
Compare Operation ops[3] with int (*ops[3])(int, int). Same meaning, much easier to read.
💡 Tip
If you've used Python, this is similar to passing a function to sorted(items, key=...). C just makes you spell out the function's type. See Python functions for a comparison.
Common mistakes with function pointers
1. Forgetting the parentheses around *name
int *op(int, int); /* declares a function, not a pointer */
Without (*op), you've declared a function that returns int *. Assigning op = add; afterwards is a compile error.
2. Calling the function instead of passing it
show_price("Half price", price, half_price(price)); /* wrong */
This passes the result (a double), not the function. GCC reports an incompatible type for the argument. Pass the name only: half_price.
3. Mismatched signatures
Assigning a function that takes (double) to a pointer declared for (int, int) triggers an "incompatible pointer type" diagnostic. Calling through a pointer of the wrong type is undefined behavior, so fix the types instead of adding a cast.
4. Comparator that reads the wrong type
In a string comparator, casting a straight to const char * treats the pointer itself as text. The sort runs but gives nonsense or crashes. Remember: qsort gives you a pointer to each element.
Practice exercise
🏋️ Try it yourself
Write void apply_to_all(int values[], int count, int (*change)(int)) that replaces every element with change(element). Write double_it and square. Starting from {1, 2, 3, 4}, double the array and print it, then square the result and print it again.
Show solution
#include <stdio.h>
int double_it(int n) {
return n * 2;
}
int square(int n) {
return n * n;
}
void apply_to_all(int values[], int count, int (*change)(int)) {
for (int i = 0; i < count; i++) {
values[i] = change(values[i]);
}
}
void print_values(const char *label, const int values[], int count) {
printf("%s:", label);
for (int i = 0; i < count; i++) {
printf(" %d", values[i]);
}
printf("\n");
}
int main(void) {
int values[4] = {1, 2, 3, 4};
apply_to_all(values, 4, double_it);
print_values("Doubled", values, 4);
apply_to_all(values, 4, square);
print_values("Squared", values, 4);
return 0;
}
Doubled: 2 4 6 8
Squared: 4 16 36 64
What's next?
You've now seen pointers to data, to pointers and to functions. Before moving on to memory management, it's worth learning where pointers usually go wrong. Next up: common pointer mistakes in C and how to avoid them.
Key takeaways
- A function pointer stores the address of a function, and calling it runs that function.
- Declare one with the function's signature and (*name), for example int (*op)(int, int).
- A function name without parentheses is its address, so op = add and op = &add are the same.
- Callbacks let another function decide when to call your code, as qsort does with a comparator.
- An array of function pointers turns a menu choice into an index, but always validate the index.
- A qsort comparator receives const void * pointers to elements and returns negative, zero or positive.
- typedef gives a function pointer type a short name, such as typedef int (*Operation)(int, int).
Frequently asked questions
What is a function pointer in C?
A function pointer is a variable that holds the address of a function. You can assign it any function with a matching signature and call that function through the pointer, for example op(2, 3).
How do you declare a function pointer in C?
Write the return type, then (*name), then the parameter types: int (*op)(int, int); declares op as a pointer to a function that takes two ints and returns an int.
Do I need & and * when using function pointers?
No. op = add and op = &add are equivalent, and so are op(1, 2) and (*op)(1, 2). Most modern code uses the shorter forms.
What is a callback function in C?
A callback is a function you pass to another function through a function pointer, so the receiver can call it later. The comparator you give to qsort is a classic callback.
Why does a qsort comparator take const void * parameters?
Because qsort works with arrays of any type, it passes generic pointers to two elements. Your comparator converts them to the real element type before comparing.