Compiled code lives in memory just like data does, so a function has an address. A function pointer is a variable that stores one - and once you can store a function, you can pass one as an argument, keep a table of them, or choose between them at runtime.
That is the capability behind sorting with a custom order, event handlers, plugin interfaces, and state machines. It is also the piece of C syntax most likely to make a reader stop and squint, so we start there.
The Declaration Syntax
Take an ordinary function:
int add(int a, int b);
To declare a pointer that can hold its address, replace the name with (*name):
int (*op)(int, int);
Read it from the inside: op is a pointer, to a function, taking (int, int), returning int.
The parentheses are not decoration. Without them, * binds to the return type instead:
int (*f)(int); // pointer to a function taking int, returning int
int *g(int); // FUNCTION taking int, returning int* - completely different
Here it is working end to end:
Both call forms work because a function designator converts to a pointer automatically. Write op(10, 3); the (*op)(10, 3) spelling is a holdover.
One note on that last printf: %p expects an object pointer, and function pointers are formally a separate family, so printing one is not strictly portable. The cast keeps compilers quiet on the platforms where it works at all; you will rarely need to print one.
typedef Makes It Readable
The declaration syntax gets ugly fast, and typedef is the standard remedy:
Compare int apply(BinaryOp op, int x, int y) with the raw form, int apply(int (*op)(int, int), int x, int y). Same meaning; one of them you can read at a glance. See typedef for the general rules.
Callbacks: Letting the Caller Supply Behavior
A callback is a function you hand to another function so that it can call you back at the right moment. It lets one piece of code handle the structure of a job while the caller supplies the decision.
Without function pointers you would write count_even, count_positive, and count_big - three copies of the same loop. With them, the loop is written once and the test is the parameter.
The Real Payoff: qsort
The standard library's qsort is the function-pointer example you will actually use. It sorts an array of anything, and it manages that by knowing nothing about the elements except their size and a comparator you provide.
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
The comparator receives two const void * - addresses of two elements - and returns a negative number if the first sorts before the second, zero if they tie, positive if it sorts after.
Two habits to carry from that comparator. Never write return x - y; - it looks clever and overflows for large or negative values, producing a wrong sign and a mis-sorted array. Cast before dereferencing, not after: *(const int *)a, because *a on a void * has nothing to read.
The same machinery sorts structs by any field you like:
The (q->score > p->score) - (q->score < p->score) idiom returns exactly -1, 0, or 1 with no subtraction and no overflow. And strcmp already has the right return convention, so a string comparator is a one-liner.
Tables of Function Pointers
An array of function pointers turns a chain of if/else into a lookup. This is how interpreters dispatch opcodes and how menu systems run commands.
Adding an operation is now adding a row, not editing a switch. Pair that with structs and you have the standard C plugin shape: a struct of function pointers is what "an interface" means in this language.
Things That Bite
Signatures must match exactly. A function pointer's type includes its parameter types and return type. Assigning a mismatched function is a constraint violation, and calling through a wrongly-typed pointer is undefined behavior even when it "works". Do not cast a comparator's signature to silence a warning - fix the comparator.
A null function pointer is still a null pointer. Initialize to NULL and check before calling, exactly as with data pointers:
if (handler != NULL) {
handler(event);
}
See null pointers for why that check earns its keep.
Void pointers lose type safety, and you are the type system. Inside a qsort comparator, nothing stops you casting to the wrong type. If you sort an array of double with compare_ints, it compiles cleanly and produces nonsense.
The declarator syntax nests. A function returning a function pointer is int (*get_op(char c))(int, int);. When you find yourself writing that, reach for typedef:
typedef int (*BinaryOp)(int, int);
BinaryOp get_op(char c); // the same thing, readable
Frequently Asked Questions
How do you declare a function pointer in C?
Write the function's signature with (*name) where the function name would go: int (*op)(int, int); declares op as a pointer to a function taking two ints and returning int. The parentheses around *op are required - without them you declare a function returning a pointer.
How do you call a function through a pointer?
Either op(3, 4) or (*op)(3, 4) - both are legal and do the same thing, because a function designator converts to a pointer automatically. Modern C code uses the plain op(3, 4) form.
What is a callback in C?
A function you hand to another function so it can call you back. qsort is the classic example: you pass a comparator, and qsort calls it whenever it needs to know which of two elements comes first. That is what lets one sort routine work on any type.
Do you need the & when taking a function's address?
No. A function name already converts to a pointer to that function, so op = add; and op = &add; are equivalent. Most C code omits the &.