5.1.13 · D1C Programming

Foundations — Function pointers — declaration, calling, use in callbacks

1,762 words8 min readBack to topic

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 (int here). 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 runs
  • add(3,4)run add with 3 and 4 → get 7

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

Memory as numbered lockers

Address

and operator gives address

Pointer holds an address

star follows a pointer

Function is code in lockers

Signature is the type of a function

Name decays to its address

parens run it now

Function pointer

Callbacks and dispatch tables


Equipment checklist

Cover the right side. If any answer surprises you, re-read that section before the parent note.

An address is
the locker-number where something's bytes begin in memory.
Code as well as data lives in
numbered memory lockers, so functions have addresses too.
&x gives you
the address of x, not its value.
*p (in an expression) does
follows the address in 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
its return type plus its parameter types — the "type" of the function.
A bare function name (no parentheses) decays to
its own code-address.
add, &add, and the pointer type they share are
the same address, of type 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
dereferencing a function pointer yields a function that decays right back to a pointer.
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
without them int *p(int,int) declares a function returning int*.