Function Pointers & Callbacks

Just as a pointer can hold a variable's address, a function pointer can hold a function's address — letting you pass functions themselves as data, store them in variables, and call them indirectly.

Every function you write lives at some address in memory, just like every variable does. A function pointer is a variable whose job is to hold that address instead of an ordinary value, which means a function can be stored, passed around, returned, and invoked indirectly — exactly the way an int or a struct can be handed from place to place. A callback builds directly on top of that idea: it is simply a function passed to another function, through a function pointer, so the receiving code can call it back later. This page walks through the whole idea in one pass — the syntax, why the parentheses matter, how to call through a function pointer, the callback pattern that shows up throughout the C standard library (qsort() being the classic example), function-pointer arrays and dispatch tables, typedef for readability, void* context parameters, state machines, and the handful of mistakes that trip up almost everyone the first time.

What Is a Function Pointer?

An ordinary pointer stores the address of an object. int *p = &x; makes p hold the location where x's value lives. A function pointer is the same idea applied to code instead of data: it stores the address where a function's executable code begins, so that address can later be used to call that function.

p and fp are conceptually parallel: p points to where x's data lives, fp points to where hello's code lives. Neither one holds a value directly — each holds an address that has to be followed before anything happens.

fp does not contain hello's code — it contains the address where that code starts. Calling fp means following that address and jumping to the code found there.

int x = 10;
int *p = &x;      // p holds the address of an object

void hello(void)
{
    printf("Hello\n");
}

void (*fp)(void);  // fp holds the address of a function
fp = hello;        // fp now points at hello
Function pointer
      |
      v
+---------------+
| address of    |
| hello()       |
+-------+-------+
        |
        v
   +---------+
   | hello() |
   +---------+

Basic Declaration Syntax

The general form for declaring a function pointer is return_type (*pointer_name)(parameter_types);. Every part matters: the return type and the parameter list describe exactly which kind of function this pointer is allowed to point at.

operation is a pointer to a function that takes two int arguments and returns int. Declaring operation does not create a function — it only creates a variable capable of pointing at one.

It's easy to misread int (*operation)(int, int); as somehow declaring a function named operation. It does not — operation is a variable of pointer type. No function body is created by this line, and operation holds garbage (an indeterminate value) until it is explicitly assigned.

return_type (*pointer_name)(parameter_types);

// example:
int (*operation)(int, int);

Why the Parentheses Around *name Matter

Two declarations that look almost identical mean completely different things, and the only difference is a pair of parentheses.

Without parentheses, * binds to int, making operation a function whose return type is int *. With parentheses around *operation, the * binds to operation itself, making operation a pointer to a function that returns plain int.

Common mistake: writing int *fp(int, int); when you meant a function pointer. This actually declares a function named fp that returns int *, which is a completely different declaration. The fix is to always parenthesize the pointer name: int (*fp)(int, int);.

int *operation(int, int);   // operation is a FUNCTION returning int *
int (*operation)(int, int); // operation is a POINTER to a function returning int

Assigning a Function to a Function Pointer

Once a function pointer variable exists, it can be pointed at any function whose signature matches. A function's name, used without parentheses, evaluates to its address — so the name alone is enough to assign it.

add by itself decays to the function's address, just as an array name decays to a pointer to its first element. &add makes that address-taking explicit, but both lines assign the exact same address to operation.

int add(int a, int b)
{
    return a + b;
}

int (*operation)(int, int);

operation = add;    // implicit address-of
operation = &add;   // explicit address-of, exactly equivalent

Calling Through a Function Pointer

Once a function pointer has been assigned, calling through it looks almost identical to calling a normal function — C allows two equivalent forms.

operation(10, 20) and (*operation)(10, 20) invoke exactly the same function call. The first form is far more common in everyday code; the second makes the fact that a dereference is happening more visible, and both compile to identical behavior.

operation = add;

int result1 = operation(10, 20);    // ordinary-looking call syntax
int result2 = (*operation)(10, 20); // explicit dereference-then-call syntax

A Complete Basic Example

This program declares a function-pointer variable, points it at add, and calls through it exactly as if operation were add itself.

operation is declared as a pointer to a function taking (int, int) and returning int.

operation = add; stores add's address inside operation.

operation(10, 20) follows that address and runs add's code with arguments 10 and 20.

add returns 30, which is stored in result and printed.

#include <stdio.h>

int add(int a, int b)
{
    return a + b;
}

int main(void)
{
    int (*operation)(int, int);

    operation = add;
    int result = operation(10, 20);

    printf("Result = %d\n", result);

    return 0;
}
add()
 |
 | address
 v
operation
 |
 | call
 v
add(10, 20)
 |
 v
30

Function Pointer Type Compatibility

A function pointer's type is defined by its return type together with its full parameter-type list. A function pointer can only be safely assigned a function whose return type and parameter types match exactly.

fp1, fp2, and fp3 are three distinct function-pointer types even though they all superficially look like pointers to two-argument functions. Only fp1 can correctly hold add's address.

Common mistake: assigning a function to an incompatible function-pointer type, e.g. int (*fp)(int); fp = some_function_returning_double; The signatures do not match, and calling through fp afterward is undefined behavior — the compiler will typically warn about this, and that warning should never be ignored.

int add(int a, int b);

int (*fp1)(int, int);       // matches add's signature exactly
double (*fp2)(int, int);    // DIFFERENT type: different return type
int (*fp3)(double, double); // DIFFERENT type: different parameter types

Passing a Function Pointer as an Argument

A function pointer becomes much more useful once it can be passed into another function as a parameter — the receiving function can then invoke whatever function was handed to it, without needing to know in advance which one that will be.

calculate() takes a, b, and a function pointer named operation. It has no idea what operation actually does — addition, subtraction, or anything else — it simply forwards a and b to whatever function it was given.

This is the foundation of a callback: a function receives another function's address and calls it on the caller's behalf.

#include <stdio.h>

int add(int a, int b)
{
    return a + b;
}

int calculate(int a, int b, int (*operation)(int, int))
{
    return operation(a, b);
}

int main(void)
{
    int result = calculate(10, 20, add);
    printf("%d\n", result);
    return 0;
}
calculate()
                  |
        +--------+---------+
        |                  |
       10                 20
        |                  |
        +--------+---------+
                 |
           operation pointer
                 |
                 v
               add()
                 |
                 v
                30

What Is a Callback?

A callback is a function supplied to another function, typically through a function pointer, so that the receiving function can invoke it — call it back — whenever it decides the moment is right.

print_value is the callback here. The caller (main) chooses which behavior to supply; process() decides when that behavior actually runs.

#include <stdio.h>

void print_value(int value)
{
    printf("Value = %d\n", value);
}

void process(int value, void (*callback)(int))
{
    callback(value);
}

int main(void)
{
    process(42, print_value);
    return 0;
}
Caller
  |
  | gives print_value()
  v
process()
  |
  | callback(42)
  v
print_value(42)

Why Callbacks Are Useful: Mechanism vs Policy

Without a callback, a function's behavior is hard-wired into its body — changing what it does means editing its source code. A callback lets one piece of code own the general mechanism (how and when something happens) while a completely different piece of code supplies the policy (exactly what happens).

process_flex() itself never changes. Swapping print_decimal for print_double changes the outcome entirely, without touching process_flex()'s code at all — the processing mechanism is fixed, the behavior it triggers is not.

// Without a callback: tied to one fixed action
void process(int value)
{
    printf("%d\n", value);
}

// With a callback: the action becomes a parameter
void process_flex(int value, void (*callback)(int))
{
    callback(value);
}

void print_decimal(int value) { printf("%d\n", value); }
void print_double(int value)  { printf("%d\n", value * 2); }

// process_flex(10, print_decimal);
// process_flex(10, print_double);

The General Callback Pattern

Nearly every callback in C follows the same shape: a plain function matching some agreed-upon signature, and a second function that accepts a pointer to that signature as one of its parameters and calls through it.

return_type callback_name(parameters);

void function_using_callback(/* other args, */
                             return_type (*callback)(parameters))
{
    callback(/* matching arguments */);
}
+------------------------+
|      Caller            |
|                        |
| chooses callback       |
+-----------+------------+
            |
            v
+------------------------+
| Function receiving     |
| callback pointer       |
+-----------+------------+
            |
            | invokes
            v
+------------------------+
| Callback function      |
+------------------------+

Callbacks with Return Values

A callback is not restricted to returning void — it can compute and return a value just like any other function, and the function invoking it can use that returned value however it needs to.

apply() forwards value to whichever callback it was given and hands the callback's own return value straight back to its caller — printing 25 then 125.

#include <stdio.h>

int square(int x) { return x * x; }
int cube(int x)   { return x * x * x; }

int apply(int value, int (*operation)(int))
{
    return operation(value);
}

int main(void)
{
    printf("%d\n", apply(5, square));
    printf("%d\n", apply(5, cube));
    return 0;
}

Reassigning a Function Pointer to Different Functions

Because a function pointer is an ordinary variable, it can be reassigned at any time, and the very next call through it will invoke whichever function it was most recently pointed at.

operation itself never changes type — only the address it currently holds changes, and that address alone decides which function actually runs.

#include <stdio.h>

int add(int a, int b)      { return a + b; }
int subtract(int a, int b) { return a - b; }

int main(void)
{
    int (*operation)(int, int);

    operation = add;
    printf("%d\n", operation(10, 5));   // 15

    operation = subtract;
    printf("%d\n", operation(10, 5));   // 5

    return 0;
}

Arrays of Function Pointers: Dispatch Tables

Function pointers can be stored in an array just like any other value. An array of function pointers, indexed by some identifier, is called a dispatch table — it replaces long if/else-if chains with a single lookup.

#include <stdio.h>

int add(int a, int b)      { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }

int main(void)
{
    int (*operations[])(int, int) = { add, subtract, multiply };

    printf("%d\n", operations[0](10, 5)); // 15
    printf("%d\n", operations[1](10, 5)); // 5
    printf("%d\n", operations[2](10, 5)); // 50

    return 0;
}
operations
   |
   +-- [0] --> add()
   |
   +-- [1] --> subtract()
   |
   +-- [2] --> multiply()

Using a Function-Pointer Array for Menu Dispatch

A very common use of a function-pointer array is selecting an operation by a numeric choice — a menu index, a command code, an opcode — instead of writing a chain of comparisons.

Instead of if (choice == 0) ... else if (choice == 1) ... else if (choice == 2) ..., the choice is used directly as an array index into the table of behaviors.

#include <stdio.h>

int add(int a, int b)      { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }

int main(void)
{
    int (*operations[])(int, int) = { add, subtract, multiply };

    int choice = 1; // 0 = add, 1 = subtract, 2 = multiply
    int result = operations[choice](20, 5);

    printf("%d\n", result); // 15

    return 0;
}

typedef for Function-Pointer Types

Raw function-pointer syntax is easy to get wrong and hard to read at a glance, especially once parameter lists grow. typedef lets you give the whole type a plain name.

Operation is now a first-class type name. Declaring variables, function parameters, and return types of this shape becomes as simple as using int or double.

// Without typedef
int (*operation)(int, int);

// With typedef
typedef int (*Operation)(int, int);

Operation operation;      // exactly the same meaning as above
operation = add;
int result = operation(10, 20);

typedef Makes Callback APIs Cleaner

The same typedef technique pays off even more in callback-taking function signatures, which otherwise repeat the same dense function-pointer syntax at every call site and declaration.

Callback now documents the intent directly in the parameter list — any reader immediately sees that process() expects some kind of named callback type, rather than parsing raw pointer-to-function syntax.

// Without typedef
void process(int value, void (*callback)(int));

// With typedef
typedef void (*Callback)(int);

void process(int value, Callback callback)
{
    callback(value);
}

Callbacks with a void* Context Parameter

A plain function pointer only represents the function itself — it carries no extra data along with it. The standard way to attach extra data to a callback in C is to pass a void * context parameter alongside it, which the callback receives every time it's invoked.

context lets the caller smuggle arbitrary extra state — here a prefix string — through process() and into the callback, without process() itself ever needing to know what that state is.

Common mistake: passing a context pointer to an object whose lifetime ends before the callback fires — for example a local variable's address, when the callback might run after that stack frame has returned. The stored context pointer becomes invalid, and dereferencing it is undefined behavior. Callback mechanisms never manage the lifetime of the context object for you; that responsibility stays with whoever supplied the pointer.

#include <stdio.h>

typedef void (*Callback)(int, void *);

void process(int value, Callback callback, void *context)
{
    callback(value, context);
}

void print_with_prefix(int value, void *context)
{
    const char *prefix = context;
    printf("%s: %d\n", prefix, value);
}

int main(void)
{
    const char *prefix = "Result";
    process(42, print_with_prefix, (void *)prefix);
    return 0;
}
process()
                       |
          +------------+------------+
          |                         |
    callback pointer             context
          |                         |
          v                         v
 print_with_prefix()             "Result"
          |
          +------------+------------+
                       v
                 Result: 42

void* and Callbacks: Recovering the Expected Type

void * can point at an object of any object type, which is exactly what makes it useful for passing arbitrary context — but the callback has to know, by agreement, what type is really hiding behind that void * and cast it back before using it.

context is reinterpreted as int * because both sides of this API — whoever calls process() and this callback — have agreed in advance that the context will always be an int *.

void * buys convenience, not safety. Nothing stops a caller from passing the address of the wrong kind of object; if the assumed type doesn't match what's actually there, the cast back compiles fine and the bug only shows up as garbage data or a crash at runtime.

void callback(void *context)
{
    int *value = context;   // recover the expected type
    printf("%d\n", *value);
}

The Standard Library Example: qsort()

One of the most important real-world examples of a callback in C is the standard library's qsort() function. Its comparator argument is a plain function pointer, and it is the single piece that determines the sort order of the whole call.

qsort() has no idea what an int array even means — it works purely in terms of raw bytes, a per-element size, and a comparator that tells it how any two elements compare. compare_ints supplies exactly that ordering logic.

Common mistake: writing a comparator whose signature doesn't match what qsort() expects — int (*)(const void *, const void *). If your function's parameter or return types don't match that exact type, the call either fails to compile with a warning or, worse, compiles with an incompatible function pointer and produces undefined behavior when qsort() invokes it.

#include <stdio.h>
#include <stdlib.h>

int compare_ints(const void *a, const void *b)
{
    int x = *(const int *)a;
    int y = *(const int *)b;
    if (x < y) return -1;
    if (x > y) return 1;
    return 0;
}

int main(void)
{
    int numbers[] = {4, 1, 5, 2, 3};
    size_t count = sizeof(numbers) / sizeof(numbers[0]);

    qsort(numbers, count, sizeof(numbers[0]), compare_ints);

    for (size_t i = 0; i < count; i++)
        printf("%d ", numbers[i]);
    printf("\n");

    return 0;
}

The Callback Flow Inside qsort()

Tracing what actually happens when qsort() runs makes the mechanism-versus-policy idea concrete: the library owns the sorting algorithm, and your comparator owns nothing except the answer to "which of these two elements comes first?" qsort() provides the sorting mechanism — the algorithm, the element movement, the recursion or partitioning. Your comparator provides the ordering policy — what "less than", "equal", and "greater than" mean for your specific data. Swapping the comparator changes the sort order without changing a single line of qsort() itself.

Your program
     |
     | gives comparator
     v
   qsort()
     |
     | compares elements
     v
 comparator(a, b)
     |
     v
 returns ordering result
     |
     v
 qsort() continues sorting

Function Pointers for State Machines

A state machine can represent each of its states as a function, and store "the current state" as a function pointer. Transitioning to a new state is then just reassigning that pointer.

Each state's behavior lives entirely inside its own function. The state machine's "current state" is nothing more than which function current_state happens to be pointing at right now.

#include <stdio.h>

typedef void (*StateFunction)(void);

void idle_state(void)    { printf("IDLE\n"); }
void running_state(void) { printf("RUNNING\n"); }
void error_state(void)   { printf("ERROR\n"); }

int main(void)
{
    StateFunction current_state = idle_state;
    current_state();          // IDLE

    current_state = running_state;
    current_state();          // RUNNING

    current_state = error_state;
    current_state();          // ERROR

    return 0;
}
+-------------+
          | IDLE        |
          +------+------+
                 |
              start
                 |
                 v
          +-------------+
          | RUNNING     |
          +------+------+
                 |
               error
                 |
                 v
          +-------------+
          | ERROR       |
          +-------------+

Function Pointers for Event Handling

Event-driven systems often need to let application code decide what happens when an event occurs, without the code that detects the event needing to know anything about the response. Function pointers are the natural way to wire that up.

register_handler() only stores an address — it has no idea what the eventual handler will do with the event. That separation is what lets the same event-detection code serve completely different applications.

typedef void (*EventHandler)(int event);

void register_handler(EventHandler handler)
{
    /* store handler somewhere, e.g. in a global or a struct */
}

/* later, when an event actually occurs: */
// handler(event);

Function Pointers vs Direct Calls

A direct call names the function being invoked right there in the source code. An indirect call goes through a function pointer instead, so the actual target is only known at run time, based on whatever address the pointer currently holds.

Indirect calls trade a small amount of directness for flexibility: the exact function invoked by operation(10, 20) can change from one run of the program to the next, or even from one call to the next, simply by reassigning operation beforehand.

// Direct call: target is fixed at compile time
add(10, 20);

// Indirect call: target is decided at run time
operation(10, 20);
Direct:
code ---------------> add()

Indirect:
code ---> operation ---> add()

Function Pointer vs Object Pointer

Function pointers and object pointers look superficially similar but belong to genuinely different pointer categories in C, and the two should not be treated interchangeably.

p points into ordinary data storage; fp points into code. C does not guarantee that the two are even the same size or representation on every platform.

Converting a function pointer to void * (an object-pointer type) and back is not a portable, general-purpose technique in standard C — void * is only guaranteed to round-trip other object pointers, not function pointers. Some platforms allow it as an extension, but don't rely on it in portable code; keep function pointers and object pointers in their own separate categories.

int *p;          // object pointer: points to an int object
int (*fp)(int);  // function pointer: points to a function

NULL Function Pointers

Just like an object pointer, a function pointer can be given the null pointer value to indicate that it currently doesn't point at any valid function — and, just like an object pointer, it must be checked before being called.

Checking callback != NULL before calling it guards against invoking through a pointer that was never assigned a real function.

Common mistake: calling a function pointer without first confirming it isn't NULL. void (*fp)(void) = NULL; fp(); is undefined behavior — it does not politely do nothing, it can crash the program or behave unpredictably. Always guard the call with an if (fp != NULL) check whenever a null value is possible.

void (*callback)(int) = NULL;

if (callback != NULL) {
    callback(10);
}

const with Function Parameters

It's easy to conflate const applied to a data pointer with something about the function pointer sitting right next to it in the same parameter list — but they are unrelated qualifications on two different kinds of pointers.

const int *data means process() promises not to modify the integers that data points to. int (*operation)(int) is a completely separate parameter — a function pointer — and const there would describe something different (making the operation variable itself unmodifiable), not the data operation eventually touches.

Don't confuse const on an object pointer's pointed-to data with any property of a function pointer's own type. They are independent concepts that happen to appear side by side in a parameter list, not two spellings of the same idea.

void process(const int *data,
             size_t count,
             int (*operation)(int));

Function Pointers and Recursion / Indirect Calls

A function can receive a pointer to a function and use it recursively or indirectly — a generic processing function might invoke a callback, and that callback might in turn call another function, potentially even one it receives as its own function-pointer argument.

run_task() calls itself directly (ordinary recursion) while also calling task indirectly through a function pointer on every level. Nothing prevents combining recursion and indirection, but the resulting call graph should be kept simple enough to reason about.

typedef void (*Task)(int);

void run_task(Task task, int value)
{
    if (value > 0) {
        task(value);        // indirect call
        run_task(task, value - 1); // ordinary recursion, same task carried forward
    }
}

Reading Complex Function-Pointer Declarations "Inside-Out"

As parameter lists grow, function-pointer declarations get visually dense. Reading them from the identifier outward — rather than left to right — makes even complicated ones tractable.

Read as: fp is a pointer to a function taking int, double, and char, and returning int. Start at the name, note the * makes it a pointer, then read the parameter list, and finally the return type left outside the parentheses.

int (*fp)(int, double, char);

//              fp
//              |
//              v
//         (*fp)
//           |
//           v
//        function
//           |
//      +----+--------+
//      v    v        v
//     int double    char
//           |
//           v
//       returns int

A Function Pointer That Returns a Pointer

The same inside-out reading handles a function pointer whose target function itself returns a pointer, rather than an ordinary value.

int *(*fp)(int) means: fp is a pointer to a function that takes an int and returns int *. The outer * (right after the closing parenthesis of the parameter list) belongs to the return type, while (*fp) is what marks fp itself as a pointer to that function.

int *get_value(int x)
{
    static int result;
    result = x * 2;
    return &result;
}

int *(*fp)(int) = get_value;

A Function That Returns a Function Pointer

C functions cannot return another function directly, but a function can return a pointer to a function — which lets a function itself be chosen and handed back as a result, most cleanly written with a typedef.

#include <stdio.h>

typedef int (*Operation)(int, int);

int add(int a, int b)      { return a + b; }
int subtract(int a, int b) { return a - b; }

Operation select_operation(int choice)
{
    if (choice == 0)
        return add;
    return subtract;
}

int main(void)
{
    Operation op = select_operation(0);
    int result = op(10, 5);
    printf("%d\n", result); // 15
    return 0;
}
select_operation(0)
        |
        v
     returns
        |
        v
      add()
        |
        v
      op pointer
        |
        v
    op(10, 5)

Dispatch Tables in More Depth

Combining an array of function pointers with a typedef produces a full dispatch table — a data structure that maps identifiers (indices, command codes, event IDs) directly to the functions that should handle them, avoiding long conditional chains entirely.

Dispatch tables scale far better than if/else-if chains as the number of commands grows — adding a new command is one new array entry, not a new branch to insert in the right place.

#include <stdio.h>

typedef void (*CommandHandler)(void);

void start(void) { printf("Start\n"); }
void stop(void)  { printf("Stop\n"); }
void reset(void) { printf("Reset\n"); }

int main(void)
{
    CommandHandler commands[] = { start, stop, reset };

    commands[0](); // Start
    commands[1](); // Stop
    commands[2](); // Reset

    return 0;
}
Command ID       Function
    0       ---> start()
    1       ---> stop()
    2       ---> reset()

Callbacks vs Function Pointers: Related but Different

It's worth keeping these two ideas distinct even though they're closely tied together. A function pointer is a mechanism — a kind of variable. A callback is a usage pattern built on top of that mechanism — a function passed somewhere specifically so it can be invoked later.

A callback is commonly implemented using a function pointer, but a function pointer doesn't necessarily have to be used as a callback — a dispatch table, for instance, uses function pointers purely for lookup and dispatch, without fitting the traditional "pass me a function to call back later" callback pattern at all.

// Function pointer: the mechanism
int (*fp)(int);

// Callback: a usage pattern built on that mechanism
void process(int value, int (*callback)(int))
{
    int result = callback(value);
}

Best Practices

A handful of habits keep function-pointer and callback code readable and safe as it grows past small examples.

Beyond the syntax itself, document every callback contract clearly: its parameters and return value, who owns any memory involved, how long supplied data needs to remain valid, whether NULL is an acceptable value, exactly when the callback will be invoked, and whether it's allowed to modify the data it's given. Keep each callback's responsibility narrow and well defined, and always double-check that any context pointer you hand to a callback will still be valid at the moment that callback actually runs.

// Prefer a typedef for complex callback types
typedef void (*EventHandler)(int);
// over repeatedly writing: void (*handler)(int);

// Validate callbacks when necessary
if (callback != NULL) {
    callback(value);
}

Key Mental Model

Two nested ideas cover almost everything on this page. Hold on to both, and the syntax stops feeling arbitrary.

The key idea underneath both diagrams is the same: the function performing the general work does not have to hard-code what should happen next. It can receive that behavior as data, through a function pointer, and decide only when — not what — to run.

+-----------------------+
| Function Pointer      |
|                       |
| stores target         |
| function address      |
+-----------+-----------+
            |
            v
      +-----------+
      | Function  |
      +-----------+
            |
            v
        execution

+-------------+
| Caller      |
+------+------+
       | supplies callback
       v
+-------------+
| Generic     |
| function    |
+------+------+
       | invokes
       v
+-------------+
| Callback    |
| function    |
+-------------+

Quick Reference

What is the real difference between int *fp(int, int); and int (*fp)(int, int);?

The first declares a function named fp that takes two ints and returns int * — it's an ordinary function declaration. The second declares fp as a variable: a pointer to a function taking two ints and returning plain int. The parentheses around *fp are what make fp a pointer rather than a function name.

Why must a function pointer's type match the return type and parameter types of the function it points to?

A function pointer's type fully describes how the target function is expected to be called — how many arguments, of what types, and what type comes back. Pointing it at a function with a different signature and calling through it is undefined behavior, because the compiler generates the call using the pointer's declared type, not the actual function's real type.

What is the conceptual difference between a function pointer and a callback?

A function pointer is a mechanism — a kind of variable that stores a function's address. A callback is a usage pattern: a function deliberately supplied to another function so it can be invoked later. Every callback is implemented with a function pointer, but not every function pointer (a dispatch table entry, for example) is being used as a callback.

In a call to qsort(), which part represents the mechanism and which part represents the policy?

qsort() itself is the mechanism — it owns the sorting algorithm and the logic for rearranging elements. The comparator function you supply is the policy — it alone decides what "comes before what" means for your specific data, without qsort() needing to know anything about your data's meaning.

Why is it risky to store a void* context pointer to a local variable and use that pointer inside a callback invoked later?

If the callback runs after the function that owned that local variable has already returned, the variable's storage may no longer be valid, so the context pointer becomes a dangling pointer. Dereferencing it inside the callback is undefined behavior — the callback mechanism itself does nothing to keep the pointed-to object alive; that's entirely the caller's responsibility.

Write a C program that declares a function pointer to a function taking two int parameters and returning int, points it at an add function, and uses it to print the sum of two numbers read from the pointer call.

operation is declared as a pointer to a function taking two ints and returning int, then pointed at add by assigning the function's name (which decays to its address). Calling operation(10, 20) follows that stored address and runs add's code, producing 30.

Write a C program that defines add, subtract, and multiply functions, stores them in an array of function pointers, and calls each one through the array to print its result.

The array operations holds three function pointers, one per arithmetic function. Indexing the array and calling the result, as in operations[0](10, 5), looks up the stored address at that index and invokes it, so each line runs a different function without any conditional logic.

Write a C program that implements a simple calculator: given two integers and a menu choice (0 for add, 1 for subtract, 2 for multiply), it uses a function-pointer array to dispatch to the correct operation and prints the result.

The menu choice is used directly as an index into the operations dispatch table instead of a chain of if/else-if comparisons. Bounds-checking choice before indexing keeps an out-of-range value from causing an invalid array access.

Write a C program with a generic process() function that takes an integer value and a callback function pointer, then calls process() twice with two different callback functions (one that prints the value plain, one that prints double the value) to show the same processing function producing different behavior.

process() always does the same thing: call whatever callback it was given with value. Passing print_decimal versus print_double changes only the behavior that runs, demonstrating how the processing mechanism and the actual policy are cleanly separated by the callback.

Write a C program that uses typedef to define a callback function-pointer type, then implements a process() function that accepts a value and a callback of that type and invokes it.

The typedef Callback names the type void (*)(int) so that process()'s parameter list reads cleanly as Callback callback instead of repeating the raw function-pointer syntax. Calling process(42, print_value) stores print_value's address and process() invokes it via callback(value).

Write a C program that implements a callback taking a void* context parameter, using it to print an integer value together with a caller-supplied prefix string.

context carries the prefix string through process() to the callback without process() itself knowing anything about strings. Inside print_with_prefix, context is cast back to const char * to recover the type both sides agreed on, then used together with value in the printed output.

Write a C program that uses the standard library qsort() function with a custom comparator to sort an array of integers in ascending order and prints the sorted array.

qsort() is handed the array, the element count, the size of each element, and compare_ints as the comparator callback. It repeatedly calls compare_ints on pairs of elements (as const void * pointers) and uses the returned sign to decide their relative order, producing an ascending sort without knowing anything about integers specifically.

Write a C program that uses qsort() with a comparator that sorts an array of integers in descending order.

Reversing the comparator's sign logic — returning -1 when the first value is greater instead of smaller — is enough to flip the sort order. qsort()'s own sorting mechanism never changes; only the ordering policy supplied by the comparator does.

Write a C program that implements a simple state machine using function pointers, with idle, running, and error states each implemented as a separate function, and transitions between them driven by reassigning a state function pointer.

current_state holds the address of whichever state's function represents the machine's current condition. Reassigning current_state to running_state or error_state and calling it again is the entire transition mechanism — each state's own printed behavior lives inside its own dedicated function.

Write a C program that defines a function returning a function pointer, choosing between add and subtract based on a parameter, and then calls the returned function.

select_operation() cannot return a function itself, but it can return a pointer to one, typed as Operation. Based on choice, it hands back either add's or subtract's address, and the caller stores that address in op before calling it, so the very same call op(10, 5) can produce 15 or 5 depending on which function was selected.

Syntax                       Meaning
int (*fp)(int)                Pointer to function taking int, returning int
fp = function                 Store function address
fp = &function                Explicitly obtain function address
fp(x)                         Call function through pointer
(*fp)(x)                      Explicit function-pointer call syntax
typedef int (*Op)(int)        Create function-pointer type
Op operation                  Variable of that function-pointer type
void (*callback)(int)         Common callback parameter
fp == NULL                    Function pointer is null