Foundations — Function pointers — declaration, calling, use in callbacks
Before you can read the parent note Function pointers — declaration, calling, use in callbacks with confidence, every single symbol it throws at you must already feel obvious. This page builds each one from nothing, in an order where each brick rests on the previous.
0. The stage everything stands on: memory as numbered lockers
Picture your computer's memory as one very long row of tiny lockers, each holding one byte, each with a printed number on its door. That printed number is an address.
Two kinds of things live in these lockers:
- Data — the values of your variables (a number, a character).
- Code — the actual machine instructions of your functions.
1. Variables and their types
Why the topic needs this: a function pointer's type will later have to describe a whole function (what it takes, what it gives back), so you must first accept that types carry meaning, not just size. See Arrays in C for how many same-typed lockers sit in a row.
2. The & symbol — "address of"
You already know x gives you the value in the locker. But sometimes you want the locker-number itself.
Why the topic needs this: to save a function's location, you'll ask for its address. The parent note's &add is exactly this operator, just aimed at a function instead of a variable.
3. Pointers — variables that hold addresses
Reading the syntax from the picture:
Why the topic needs this: a function pointer is just this idea with "function" swapped in for "int." Everything in Pointers in C transfers directly. Also meet void pointers — a void * is a pointer that holds an address but forgets the type; qsort uses these so it can carry any data.
4. What a function really is
Three vocabulary anchors on that picture:
- Return type — the type of the value handed back (
inthere). Answers "what shape comes out?" - Parameters / parameter types — the inputs and their types (
int, int). Answers "what must I feed in?" - Function body — the instructions between
{ }, sitting in code-lockers.
Why the topic needs this: the parent's phrase "return type and parameter types must match exactly" is meaningless until you see that the signature is a type the compiler checks.
5. Decay — a function name becomes an address
Here is the quiet magic the parent leans on heavily.
That is why the parent says all three of these mean the same address:
add→ decays to the address&add→ explicitly the address- both have type
int (*)(int, int)
6. Calling — the () operator
Contrast the two:
add→ the function (decays to its address) — nothing runsadd(3,4)→ runaddwith 3 and 4 → get7
7. Putting the symbols together — reading int (*p)(int,int)
Now every piece the parent's declaration template needs is defined. Read it inside-out, exactly like the data pointer in §3:
The tools typedef (naming this ugly type once) and qsort and the C Standard Library (the star callback user) build straight on top of this, and the whole "swap the function, change the behaviour" trick is a form of Polymorphism.
The prerequisite map
Equipment checklist
Cover the right side. If any answer surprises you, re-read that section before the parent note.
An address is
Code as well as data lives in
&x gives you
x, not its value.*p (in an expression) does
p and reads the value there.* in a declaration like int *p; means
p is a pointer" — a different job from the dereference star.A function's signature is
A bare function name (no parentheses) decays to
add, &add, and the pointer type they share are
int (*)(int,int).add(3,4) versus add differ because
() runs the function now, while the bare name is just its address.p(3,4) and (*p)(3,4) are equal because
Read int (*p)(int,int) inside-out as
p is a pointer to a function taking two ints and returning int.The parentheses around *p are needed because
int *p(int,int) declares a function returning int*.