Function Pointers in C: Declarations, Callbacks and Runnable Examples

Learn to read function-pointer declarations, build a callback dispatch table, guard invalid selections, and write a safe qsort comparator in C.

KnowledgeGate Team

Exam prep & CS education

Updated 23 Sep 20266 min read

int (*op)(int, int) looks like punctuation until you read the declaration around its identifier. A calculator can select add, subtract or multiply from a dispatch table, while qsort can receive a comparator through the same mechanism. The worked values 18 and 6 make every callback choice and result traceable.

Function pointers in C: the function signature becomes the pointer type

Start with an ordinary function:

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

Its type accepts two int arguments and returns int. In int (*op)(int, int) = add;, op stores a pointer to a compatible function.

Read from op outwards. *op says pointer. (*op)(int, int) says pointer to a function taking two int values. The leading int is the function's return type. By contrast, int *op(int, int) declares a function returning int *. The parentheses are load-bearing.

The function designator add converts to a pointer here, so op = add; and op = &add; select the same function. Keep function and object pointers separate. A void * is not a portable generic holder for a function pointer.

Calling through a function pointer: assign, redirect and trace exact values

A typedef gives the repeated signature a readable name:

c
typedef int (*BinaryOp)(int, int);

BinaryOp op = add;
int first = op(9, 4);

The last line calls add(9, 4), so first = 9 + 4 = 13. Define int multiply(int a, int b) { return a * b; }, then set op = multiply;. Now int second = (*op)(9, 4); evaluates 9 * 4 = 36. op(9, 4) and (*op)(9, 4) are equivalent; the first is easier to read.

Return and parameter types must agree. A function returning double or accepting different parameters does not fit BinaryOp. Use the correct callback type or an adapter. Casting an incompatible function pointer hides the mismatch and can make the call invalid.

Callback in C: a complete calculator dispatch program

A callback is a function supplied to other code, which can call the selected behaviour later. This complete program passes each operation to apply:

c
#include <stddef.h>
#include <stdio.h>

typedef int (*BinaryOp)(int, int);

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

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

static int multiply(int a, int b) {
    return a * b;
}

static int apply(int x, int y, BinaryOp op) {
    return op(x, y);
}

int main(void) {
    BinaryOp ops[] = {add, subtract, multiply};
    const char *names[] = {"add", "subtract", "multiply"};
    size_t count = sizeof ops / sizeof ops[0];

    for (size_t i = 0; i < count; ++i) {
        printf("%s: %d\n", names[i], apply(18, 6, ops[i]));
    }
    return 0;
}

apply receives copies of 18, 6 and one function pointer. op(x, y) invokes the selected operation:

  1. i = 0 selects add, so 18 + 6 = 24.

  2. i = 1 selects subtract, so 18 - 6 = 12.

  3. i = 2 selects multiply, so 18 * 6 = 108.

The exact output is:

Code
add: 24
subtract: 12
multiply: 108
Calculator callback flow: x = 18 and y = 6 pass through the ops table into apply, giving add 24, subtract 12 and multiply 108.

Function-pointer arrays and selector functions: dispatch safely

BinaryOp ops[3] is an array of three function pointers. By contrast, BinaryOp choose(char symbol) is a function returning one pointer.

c
static BinaryOp choose(char symbol) {
    if (symbol == '+') return add;
    if (symbol == '-') return subtract;
    if (symbol == '*') return multiply;
    return NULL;
}

For BinaryOp selected = choose('*');, check selected != NULL. Then selected(7, 5) calls multiply(7, 5) and returns 35. choose('?') returns NULL, which must not be called. Without the typedef, the declaration is int (*choose(char symbol))(int, int). Decode it once, but retain the typedef in real code.

This separates selection from execution without repeating a switch at every call site.

Only ops[0], ops[1] and ops[2] are valid, so reject menu choice 3 before indexing. Coding & DSA Courses for Placements is the broader route for programming and problem-solving practice.

qsort callback example: compare and sort five exact integers

The standard library's qsort accepts a comparator callback. Include <stdlib.h>:

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

static int compare_ints(const void *left, const void *right) {
    int a = *(const int *)left;
    int b = *(const int *)right;
    return (a > b) - (a < b);
}

int main(void) {
    int values[] = {31, 7, 18, 7, 25};
    size_t count = sizeof values / sizeof values[0];

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

    for (size_t i = 0; i < count; ++i) {
        printf("%d%s", values[i], i + 1 == count ? "\n" : " ");
    }
    return 0;
}

The array contains five elements, so count is 5. qsort passes two element addresses to compare_ints, and the casts recover pointers to const int. The loop prints the sorted array:

Code
7 7 18 25 31

As representative sign checks, not the library's call order, (7, 31) gives 0 - 1 = -1, (7, 7) gives 0 - 0 = 0, and (31, 7) gives 1 - 0 = 1. This expression avoids the overflow risk of returning a - b. The Time complexity and asymptotic notation: Big-O, Theta and Omega explained lesson explains how to analyse a particular sorting algorithm. The qsort interface itself specifies no fixed algorithm or complexity.

qsort figure: the row 31, 7, 18, 7, 25 becomes 7, 7, 18, 25, 31, with comparator sign examples returning -1, 0 and 1.

Common function-pointer errors: cause, consequence and correction

Mistake

Consequence

Correction

Omit parentheses in int (*op)(int, int)

Declares a function returning int *

Wrap *op in parentheses

Assign an incompatible signature

Gives the call the wrong contract

Use a matching typedef or adapter

Call NULL

Causes undefined behaviour

Reject choose('?') first

Index beyond ops[2]

Accesses outside the array

Reject menu index 3 first

Cast a function-type mismatch

Hides a useful diagnosis

Correct the signature

Confuse function and object pointers

Causes non-portable misuse

Keep them distinct

If a callback accepts a double, define a compatible typedef. A function pointer identifies code but captures no locals. Pass changing state through an explicit parameter, commonly a void *context object pointer documented by the callback contract, not a global. Share the typedef with caller and receiver; enable warnings to catch mismatches.

How function pointers in C are tested in tracing and declaration questions

Semester, coding-test and interview questions ask you to decode a declarator, choose a compatible function, predict callback output, trace handlers, find a null-call path, or supply a correctly typed comparator.

Take int inc(int x) { return x + 1; }, int twice(int x) { return 2 * x; }, and int (*steps[2])(int) = {inc, twice};. In steps[1](steps[0](5)), the inner call is inc(5) = 6; the outer call is twice(6) = 12; the final value is 12.

For declarations, int (*p)(int) is a pointer to a function taking int and returning int. int *p(int) is a function taking int and returning int *. int (*p[3])(int) is an array of three compatible function pointers.

After tracing these declarations by hand, practise pointer questions that ask for a compatible signature, a selected handler or the next output value.

Function pointers in C: the short version and next practice step

Keep five rules: match the full signature, retain the declarator parentheses, use a typedef when callbacks repeat, check nullable selections before calling, and validate dispatch indexes before access.

Before compiling, add maximum(14, 9) to the dispatch array and predict 14. Write minimum(14, 9) and predict 9. Finally, change the nested trace to steps[0](steps[1](5)): twice(5) = 10, then inc(10) = 11. For a structured next step through C concepts and coding questions, use the C Language Course: Concepts, MCQs and Coding.