#include <stdio.h>
void sayHello(void) {
puts("Hello!");
}
int main() {
printf("Function address: %p\n",(void *)sayHello);
return 0;
}Function Pointers and Callbacks in C: How Callback Execution Works
What is Function Pointers and Callbacks in C? Core Concept & Direct Summary
How do function pointers store memory addresses of executable code and implement runtime callback patterns like `qsort()`?
Every pointer we've discussed so far has pointed at data — an int, a char, an array. Here's the idea that breaks most people's brains the first time: in C, functions also live in memory, at specific addresses — which means you can have a pointer that stores a function's location, and call the function through that pointer.
Why on earth would you want to? Because it enables callbacks — the ability to pass a function as an argument to another function, so that function can call yours back when the time is right. It's the mechanism behind sorting with custom rules, event handling, plug-in architectures, and generally half of what makes C libraries flexible.
This is advanced territory. We'll go slowly, build up from "a function has an address" to a working callback, and finish with the two patterns where function pointers genuinely earn their reputation.
(Suggested image: the hero diagram — a function's code sitting in a labeled memory box, a pointer variable pointing to that box, and an arrow calling through the pointer. Get this visual right and the rest of the page clicks.)
How Does Function Pointers and Callbacks in C Work? In-Depth Mechanics & Architecture
Step 1: A Function Has an Address
First, prove it to yourself. The name of a function, used on its own, gives you its address — just like the name of an array:
Output (address will vary):
sayHello — no parentheses — is a pointer to the function. Add the parentheses (sayHello()) and you call it instead. That single distinction — name alone = address, name with () = call — is the seed of everything else on this page.
Step 2: Declaring a Function Pointer
A function pointer's declaration has to describe the function it can point to — return type, and parameter types:
Read it right-to-left from the variable name: "ptr is a pointer to a function taking void, returning void."
The parentheses around *ptr are mandatory. Compare:
Drop the parentheses and the meaning flips completely. This is the most common function-pointer typo in existence, and now you know what to scan for.
(Suggested image: an annotated diagram with arrows from each part of int (*calc)(int, int); to its meaning)
Step 3: Assigning and Calling
Output:
Two things to notice:
- Assigning needs no
&—operation = add;works, because a function name is its address. (Writingoperation = &add;also works, identically — both styles are accepted.) - Calling through the pointer looks like calling a normal function —
operation(5, 3). You can also write the more explicit(*operation)(5, 3), which is exactly equivalent. Modern style prefers omitting it.
The payoff is already visible: one pointer variable, two completely different behaviors, decided at runtime. That flexibility is what makes callbacks possible.
Step 4: Passing a Function as an Argument (The Baby Callback)
Now the real thing. A function can accept a function pointer as a parameter — that's a callback:
Output:
Here's the key to understanding this: welcome doesn't know — or care — which greeting function it will receive. It receives an address, and at the marked line, it jumps to whatever code lives there. That jump is the callback.
Why this matters: before, the only way welcome could produce different behavior was to change its own code. Now behavior is a parameter. You can add a French greeting tomorrow without touching welcome at all — and that's precisely what libraries exploit: their code stays fixed while you supply the behavior.
(Suggested image: THE callback flow diagram — main() passing greetSpanish's address to welcome(), then welcome() jumping through the pointer into greetSpanish's code box and returning. This is the diagram readers need most.)
The Real-World Callback: qsort()
The standard library's qsort() is the canonical callback example — it can sort any array of anything, and it asks you which "smaller than" rule to use:
That last parameter is a callback. You write the comparison; qsort does the sorting and calls your function whenever it needs to decide the order of two elements.
Sorting numbers ascending:
Output:
Change one line — get descending order:
Same qsort code, same array, different callback — different ordering. That's the callback pattern delivering real, practical value. And notice the comparison function's contract: return negative if the first item should come first, zero if they're equal, positive if the second should come first. (The void * parameters are C's "any type" pointers — you cast them to the real type inside; they're a pointers-cluster topic.)
(Suggested image: qsort with two callback arrows — "ascending" and "descending" — both feeding into the same qsort box)
Making It Readable: typedef for Function Pointers
Raw function pointer declarations get ugly fast. The typedef shortcut fixes that:
And they clarify what belongs in your array of function pointers (coming next):
Professional habit worth adopting immediately: if a function pointer type appears more than once, typedef it. int (*)(const void *, const void *) scattered three times in a file is hostile to readers.
The Menu Dispatch Pattern: An Array of Function Pointers
Remember the menu programs from our control flow cluster — switch statements mapping choices to actions? Function pointer arrays can do that job more elegantly, and they're a widely used real-world pattern:
Sample Run:
One line — menu[choice - 1]() — replaces the entire switch. Each array slot holds the address of one function, and indexing into it with the user's choice calls the matching one. Add a fifth menu option, and you add one function plus one array entry: done. No case labels to keep in sync, no break statements to forget.
This is exactly the kind of table-driven design you'll see in game engines, command processors, and device drivers.
Where Callbacks Live in the Real World
You're already using callbacks constantly, even if you didn't know:
qsort()/bsearch()— custom comparison rulessignal()— the operating system calls your function when Ctrl+C is pressed- GUI libraries — "when this button is clicked, run my handler function"
- Thread and async libraries — "when this task finishes, call this function"
- Event loops, network servers, embedded interrupts — all callback-driven
Every one of them is the same mechanism you just learned: store an address, jump to it later.
Common Function Pointer Mistakes
- Dropping the parentheses in the declaration —
int *fn(int)declares a function returning an int pointer, not a function pointer. The(*name)grouping is mandatory. - Calling a NULL or uninitialized function pointer — an unassigned function pointer points somewhere random; calling it crashes. Initialize to
NULLand check before calling: - Signature mismatch — assigning a function with a different return or parameter type produces a warning and undefined behavior at the call. The callback can never have a different shape than the pointer expects (which is why
qsortforces thevoid *contract). - Forgetting
&vs no-&confusion — both= addand= &addwork, but pick one style and be consistent; mixing confuses readers. - Overusing callbacks — for two options, a simple
ifis clearer than a function pointer. Callbacks earn their place when there are many interchangeable behaviors, or when someone else (a library, a user of your code) supplies the behavior. - Ignoring the typedef and writing the raw pointer type repeatedly — technically fine, practically unreadable.
typedefearly.
(Suggested image: "6 Function Pointer Mistakes" checklist graphic, consistent with the series)
A Complete Example: A Mini Calculator With a Dispatch Table
Everything on this page, working together:
Output:
A typedef makes the type readable, an array holds four interchangeable behaviors, and applyOperation receives any of them as a callback parameter. That's the complete pattern — and it's only a few lines.
Final Thoughts
Function pointers feel alien because we're used to functions being syntax — things you write and call directly. But in C, they're also data: addresses you can store, pass, compare, and choose between at runtime. That single reframe unlocks callbacks, and callbacks unlock the design style that most professional C code is built on.
Don't worry if this needs two or three passes. The path in is steady: understand that a function has an address → declare a pointer with matching shape → assign and call it → pass it to another function → that's a callback. Then borrow qsort's comparison pattern, and you're using the technique for real.
A heads-up for the series: this page leans on void * and casts, which are pointers-cluster topics. If any of that felt shaky, the Pointers cluster will fill the gaps properly — and this page will look obvious in retrospect.
Runnable C Code Example & Practical Implementation
#include <stdio.h>
#include <stdlib.h>
// Comparator callback for qsort
int compareAscending(const void *a, const void *b) {
return(*(int*)a - *(int*)b);
}
int main() {
int arr[] = {
45, 12, 85, 32, 89, 5
};
int n = sizeof(arr) / sizeof(arr[0]);
// Pass function pointer callback to qsort
qsort(arr, n, sizeof(int), compareAscending);
printf("Sorted array via qsort callback:\
");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\
");
return 0;
}Key Takeaways, Best Practices & Memory Safety Rules
typedef to define clean aliases for complex function pointer signatures to avoid unreadable code.Frequently Asked Questions & Technical Interview Insights
Technical Interview & Viva Questions
Frequently asked C programming interview questionsQ: How do you declare a `typedef` for a function pointer taking two `int`s and returning `int`?
Q: Where are function pointers used in operating systems?
Related Tools, Tutorials & Authoritative References
Recommended Interactive Tools
- Online C Compiler & Runner — Interactive online GCC sandbox to execute C code in real time.
- Online C Code Editor & IDE — Multi-file C programming environment with code formatting.
- C++ Programming Tutorial — Transition from procedural C to Object-Oriented C++ with STL.
- JavaScript Programming Tutorial — Master modern JavaScript fundamentals, DOM APIs, and async patterns.
Authoritative Standards & Citations
- ISO/IEC 9899 (C Language Standard) — Official international C specification by ISO/IEC JTC 1/SC 22/WG 14.
- GNU GCC Documentation — Official reference manual for the GNU Compiler Collection and C libraries.
- The C Programming Language (K&R) — Definitive classic work by Brian Kernighan & Dennis Ritchie at Bell Labs.
- C Reference Documentation (cppreference.com) — Verified reference covering C89 through C23 language features.