Function pointers in C hold the address of a function and allow calling it indirectly — through the pointer rather than by name. This enables callbacks, dispatch tables, plugin architectures, and strategy patterns that are otherwise impossible in C without conditional branches. Every event-driven framework, interrupt vector table, and generic algorithm in embedded and systems C uses function pointers.
Syntax
/* A pointer to a function that takes two ints and returns int */
int (*compare)(int, int);
/* Calling through the pointer — both forms are equivalent */
result = compare(a, b);
result = (*compare)(a, b); /* explicit dereference — also valid */
The parentheses around *compare are required — without them, int *compare(int, int) is a function that returns int *, not a pointer to a function.
typedef Cleans Up the Syntax
/* Without typedef — verbose */
int (*add)(int, int);
int (*subtract)(int, int);
/* With typedef — readable */
typedef int (*BinaryOp)(int, int);
BinaryOp add;
BinaryOp subtract;
The typedef names the type (BinaryOp) rather than a specific variable. Prefer typedef for function pointer types used in multiple places.
Function Pointer Program in C
#include <stdio.h>
/* ── Basic usage ─────────────────────────────────────────── */
typedef int (*BinaryOp)(int, int);
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; }
/* ── Passing a function pointer as an argument (callback) ── */
void apply_and_print(int x, int y, BinaryOp op, const char *name)
{
printf("%s(%d, %d) = %d\n", name, x, y, op(x, y));
}
/* ── Array of function pointers (dispatch table) ─────────── */
static BinaryOp ops[] = { add, subtract, multiply };
static const char *names[] = { "add", "subtract", "multiply" };
/* ── Generic sort comparator (like qsort) ─────────────────── */
void bubble_sort(int arr[], int n, int (*cmp)(int, int))
{
int i, j, tmp, swapped;
for (i = 0; i < n - 1; i++) {
swapped = 0;
for (j = 0; j < n - 1 - i; j++) {
if (cmp(arr[j], arr[j+1]) > 0) {
tmp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = tmp;
swapped = 1;
}
}
if (!swapped) break;
}
}
int cmp_asc (int a, int b) { return a - b; } /* ascending */
int cmp_desc(int a, int b) { return b - a; } /* descending */
/* ── State machine using function pointers ────────────────── */
typedef void (*StateFunc)(void);
void state_idle (void) { printf("State: IDLE\n"); }
void state_running(void) { printf("State: RUNNING\n"); }
void state_done (void) { printf("State: DONE\n"); }
int main(void)
{
int i, arr[] = {5, 2, 8, 1, 9, 3};
int n = (int)(sizeof(arr) / sizeof(arr[0]));
/* Assign and call through pointer */
BinaryOp op = add;
printf("Direct call: add(10, 3) = %d\n", op(10, 3));
op = multiply;
printf("Repointed: multiply(10, 3) = %d\n\n", op(10, 3));
/* Callback */
apply_and_print(10, 3, add, "add");
apply_and_print(10, 3, subtract, "subtract");
apply_and_print(10, 3, multiply, "multiply");
printf("\n");
/* Dispatch table */
printf("Dispatch table:\n");
for (i = 0; i < 3; i++)
printf(" ops[%d](%d, %d) = %d\n", i, 10, 4, ops[i](10, 4));
printf("\n");
/* Generic sort */
bubble_sort(arr, n, cmp_asc);
printf("Ascending: ");
for (i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
bubble_sort(arr, n, cmp_desc);
printf("Descending: ");
for (i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n\n");
/* State machine */
StateFunc states[] = { state_idle, state_running, state_done };
for (i = 0; i < 3; i++)
states[i](); /* call through dispatch table */
return 0;
}
How to Compile and Run
gcc -ansi -Wall -Wextra -o funcptr funcptr.c
./funcptr
Sample Output
Direct call: add(10, 3) = 13 Repointed: multiply(10, 3) = 30 add(10, 3) = 13 subtract(10, 3) = 7 multiply(10, 3) = 30 Dispatch table: ops[0](10, 4) = 14 ops[1](10, 4) = 6 ops[2](10, 4) = 40 Ascending: 1 2 3 5 8 9 Descending: 9 8 5 3 2 1 State: IDLE State: RUNNING State: DONE
How the Standard Library Uses Function Pointers
The C standard library qsort function is the canonical example:
#include <stdlib.h>
void qsort(void *base, size_t n, size_t size,
int (*compar)(const void *, const void *));
The compar function pointer is what makes qsort generic — it works for any data type by delegating the comparison decision to the caller:
#include <stdio.h>
#include <stdlib.h>
int cmp_int(const void *a, const void *b)
{
return (*(int *)a) - (*(int *)b);
}
int main(void)
{
int arr[] = {5, 2, 8, 1, 9, 3};
int i, n = (int)(sizeof(arr) / sizeof(arr[0]));
qsort(arr, (size_t)n, sizeof(int), cmp_int);
for (i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
Function Pointers in Embedded C
Interrupt vector tables are arrays of function pointers, defined at a hardware-specified memory address:
/* Conceptual interrupt vector table */
typedef void (*ISR)(void);
void default_handler(void) { /* do nothing */ }
void uart_irq(void) { /* handle UART data */ }
void timer_irq(void) { /* handle timer tick */ }
/* Vector table — address is MCU-specific */
ISR vector_table[] = {
default_handler, /* NMI */
default_handler, /* HardFault */
timer_irq, /* TIM2 */
uart_irq, /* USART1 */
};
HAL (Hardware Abstraction Layer) libraries like STM32’s also use function pointers extensively as callbacks for DMA completion, I2C events, and error conditions — the same pattern as the apply_and_print callback above.
Common Mistakes
- Missing parentheses:
int *fp(int)is a function returningint *;int (*fp)(int)is a pointer to a function. The parentheses are not optional. - Calling a NULL function pointer: always check
if (fp != NULL)before calling a callback that may not have been registered. - Mismatched signature: calling through a function pointer whose signature doesn’t match the actual function’s signature is undefined behaviour. The typedef approach prevents this — the compiler catches mismatches when you assign.
- Comparing function pointers to non-NULL values: only equality with
NULLis portable. Treating function pointers as integer addresses is implementation-defined.
What This Teaches
- Indirection through pointers applies to code, not just data: a function pointer is exactly like a data pointer — it stores an address, can be passed, stored in arrays, and reassigned
- Callbacks are just function pointers with a contract: the caller provides storage for the function pointer; the callee invokes it — this is the core pattern behind every event system
- Dispatch tables eliminate switch statements: an array of function pointers indexed by an integer state or opcode is often cleaner, faster, and more extensible than a switch/case chain
Related Topics
- Bit Manipulation in C
- volatile Keyword in C
- Bubble Sort in C
- Binary Search Tree in C
- C Aptitude Questions – Function Pointer Section
As an Amazon Associate we earn from qualifying purchases.
Recommended Book
Function pointers and the qsort/bsearch pattern are covered in section 5.11 of The C Programming Language by Kernighan & Ritchie. The table-driven lookup pattern from section 6.6 is the direct ancestor of every modern dispatch table. Also on Amazon.com.