The term "callback" is a label given to a function called indirectly
by being dereferenced via a function pointer. ...
That is one possible definition of the term "callback function",
but I contend it is not a very good one.
As [someone else] said, "Callback function" refers to usage.
This is a better definition. It is still not all that solid,
though.
A google search suggests that there is no single, fixed definition
in computing. Microsoft have two separate definitions, one for
their Windows API and one for IIS. Erlang defines a callback
function as a (particular kind of) function in a callback module.
All these definitions share a common "flavor", though, and they
all fit at least reasonably well under the "usage" label. I think
we can refine this a bit though, by splitting tasks among particular
"entities", and referring back to the English-language meaning of
"I'll call you back".
Suppose a program is split into two or more "algorithmic entities",
where the responsibility for some particular task is delegated to
some sub-entity. For instance, we might have an application that
interacts with a user, talks to a database, and runs a printer,
all at various times.
Suppose the user code calls the database software, and while that
is doing its job, it encounters a database problem and needs to do
some quick interaction with the user (while still being "mostly
database-y"). It might use a "callback" into the user-interface
code to achieve this.
Later, if the database code decides to run the printer, and the
code running the printer runs into a problem and needs information
from the database (while still being "mostly printer-y"), it might
use a "callback" into the database code. If the answer is "this
requires user interaction", but the database and printer code is
not being called *by* the user-interaction code, then a call from
the database or printer code into the user-interaction code is just
a "call", not a "call back".
There is still a lot of "wool" in this attempt to "define by example",
so let me try a more formal specification. This is just my attempt,
not any sort of "official" definition for a callback function:
A function is labeled "callback" when it is "called back" by
a separate body of code that is called to perform some specific
task. That is, code-group A calls code-group B, and code-group
B does most of the work, but occasionally needs processing by
code-group A. If that extra processing is supplied by having
code-group B call a specified function within code-group A,
the designated function in A is a "callback".
There's nothing really special about a callback function per se;
it's all about intent and usage. Usually, a callback function is
written with a specific use in mind, as in the qsort example you
used, but beyond that a callback function is simply a normal function.
Right.
The callback mechanism is implemented through function pointers.
Not necessarily.
This *is* a useful mechanism, but not the only possible one, even
in C, and in other languages only the alternative mechanisms might
be available.
Let me use a simple quicksort function again as an example. This
quicksort will use the same calling sequence as C's qsort(), except
that the comparison function pointer is omitted. That is, instead
of:
void qsort(void *base, size_t nel, size_t width,
int (*compar)(const void *, const void *));
we have (nb, I just typed this in without testing it, no guarantee
it is correct, and it is not optimized in any way):
void quicksort(void *base, size_t nel, size_t width) {
extern int qcompare(const void *, const void *);
unsigned char *b, *pivot;
unsigned char *l, *r;
size_t i, j, half;
if (nel < 2) /* empty or 1 element => already sorted */
return;
b = base;
i = 0;
j = nel - 1;
/* select "middle" item as pivot, rounding up "just because" */
half = (j + 1) / 2;
pivot = base + half;
partition:
if (i <= j) {
l = base + i * width; /* left-side item */
if (qcompare(l, pivot) < 0) {
i++;
goto partition;
}
r = base + j * width; /* right-side item */
if (qcompare(r, pivot) >= 0) {
j--; /* can get j<i here */
goto partition;
}
if (i < j) {
/* qmemswap saves item l, copies r to l, copies saved to r */
qmemswap(l, r, width);
goto partition;
}
}
/*
* now everything from [0..half) < pivot and everything from
* (half..nel) is >= pivot, and of course pivot == pivot.
* The second interval might be empty.
*/
quicksort(base, half, width);
if (nel - 1 > half)
quicksort(pivot + width, nel - half - 1, width);
}
Here quicksort() "calls back" into whatever called it by calling
the function qcompare() *using the name qcompare*.
To use this version of quicksort, instead of the (no doubt superior)
C library qsort(), you must name your function "qcompare". You
get only one comparison function per C program. But qcompare() is
still a callback function, *even though it is called directly*
(rather than via a pointer).
In other words, the callback function itself is not implemented through
function pointers but rather the means of "calling back" the
function is accomplished through function pointers.
Or, by name -- but using a pointer is "better", because in this
case, it allows us to call qsort() from any number of places, and
use different functions, not just qcompare(), to compare elements.
If I'm on track, the question of when function pointers are used in
a non-callback context is an interesting one, at least to me.
You might find a good example in a state-machine, such as this
skeleton that works with a hardware device:
struct state {
struct state (*next)(struct state);
... other elements if needed ...
};
struct state start(struct state);
struct state stop(struct state);
...
void run_state_machine(void) {
struct state s = { start };
do {
wait_for_hardware_to_be_ready();
s = s.next(s);
} while (s.next != stop);
}
...
struct state start(struct state x) {
if (some_hardware_switch)
x.next = s1;
else
x.next = s2;
return x;
}
...
These do not really qualify as "callbacks" because the entire
machine is all one body of code. Functions start(), s1(), s2(),
s3(), ..., stop() are all provided as a single package, and the
"driver" function -- run_state_machine() -- is permanently attached
to the same package, by virtue of waiting for the hardware in
question before each step.
Of course, you could "re-divide" the task here, so that the state
machine is separate from the hardware-interation. If you did that,
each state-handler *would* suddenly qualify for the "callback"
label. But this is as it should be: if you restructure the code
and move responsibilities around, it becomes different code, using
the same mechanisms for different purposes, so it may well deserve
different labels.