Function pointers are a powerful feature in the C programming language. While they are often introduced as a way to store and call functions indirectly, their true value emerges in more advanced programming patterns. In this article, we’ll explore six advanced applications of C function pointers, complete with practical examples.
1. Callback Functions #
A callback function is registered in advance and executed when a specific event occurs. This mechanism allows you to decouple event handling logic from the main program flow.
#include <stdio.h>
void handle_event(int event_type, void (*callback)(void))
{
printf("event %d occurred\n", event_type);
if (callback) {
callback();
}
}
void callback_function()
{
printf("callback function called\n");
}
int main()
{
handle_event(1, callback_function); // with callback
handle_event(2, NULL); // no callback
return 0;
}
Here, handle_event
triggers a function dynamically through a pointer. This pattern is widely used in GUI frameworks, drivers, and signal handlers.
2. Function Parameterization #
Function pointers let you parameterize behavior dynamically, making your functions more reusable.
#include <stdio.h>
void process_array(int *array, size_t size, int (*process)(int))
{
for(size_t i = 0; i < size; i++) {
array[i] = process(array[i]);
}
}
int increment(int n) { return n + 1; }
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
size_t size = sizeof(array) / sizeof(int);
process_array(array, size, increment);
for (size_t i = 0; i < size; i++) {
printf("%d ", array[i]);
}
printf("\n");
return 0;
}
This allows different transformations (e.g., increment, square, negate) to be plugged in without rewriting the loop.
3. Sorting Algorithms #
By passing comparison functions, you can reuse the same sorting code for multiple orderings.
#include <stdio.h>
#include <stdlib.h>
typedef int (*compare_func_t)(const void *, const void *);
void sort(int *array, size_t size, compare_func_t compare_func)
{
qsort(array, size, sizeof(int), compare_func);
}
int compare_int(const void *a, const void *b)
{
return (*(int *)a - *(int *)b);
}
int compare_reverse_int(const void *a, const void *b)
{
return (*(int *)b - *(int *)a);
}
int main()
{
int array[] = { 3, 1, 4, 1, 5, 9 };
size_t size = sizeof(array) / sizeof(int);
sort(array, size, compare_int); // ascending
sort(array, size, compare_reverse_int); // descending
for (size_t i = 0; i < size; i++) {
printf("%d ", array[i]);
}
printf("\n");
return 0;
}
This approach is used by the C standard library’s qsort
, making your sorting functions flexible and reusable.
4. Function Pointer Arrays (Dispatch Tables) #
Arrays of function pointers can act as dispatch tables, selecting behavior at runtime.
#include <stdio.h>
void add(int a, int b) { printf("%d + %d = %d\n", a, b, a + b); }
void subtract(int a, int b) { printf("%d - %d = %d\n", a, b, a - b); }
void multiply(int a, int b) { printf("%d * %d = %d\n", a, b, a * b); }
void divide(int a, int b) { if (b) printf("%d / %d = %d\n", a, b, a / b); else printf("cannot divide by zero\n"); }
typedef void (*operation_func_t)(int, int);
int main()
{
operation_func_t operations[] = { add, subtract, multiply, divide };
int a = 10, b = 5;
for (size_t i = 0; i < 4; i++) {
operations[i](a, b);
}
return 0;
}
This is often used in interpreters, finite-state machines, and command dispatch systems.
5. Backtracking with Function Pointers #
Function pointers can act as custom callbacks in recursive algorithms such as backtracking.
#include <stdio.h>
typedef void (*callback_func_t)(const int *, size_t);
void swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; }
void permute(int *nums, size_t len, size_t depth, callback_func_t callback)
{
if (depth == len) {
callback(nums, len);
return;
}
for (size_t i = depth; i < len; i++) {
swap(&nums[depth], &nums[i]);
permute(nums, len, depth + 1, callback);
swap(&nums[depth], &nums[i]);
}
}
void print_array(const int *arr, size_t len)
{
for (size_t i = 0; i < len; i++) printf("%d ", arr[i]);
printf("\n");
}
int main()
{
int nums[] = { 1, 2, 3 };
permute(nums, 3, 0, print_array);
return 0;
}
By plugging in different callbacks, the permutation algorithm could count, filter, or store solutions instead of just printing them.
6. Implementing Polymorphism in C #
Although C is not object-oriented, you can simulate polymorphism using structs and function pointers.
#include <stdio.h>
typedef struct shape {
void (*draw)(struct shape *);
} shape_t;
typedef struct circle {
shape_t shape;
int x, y, r;
} circle_t;
typedef struct rectangle {
shape_t shape;
int x, y, w, h;
} rectangle_t;
void circle_draw(shape_t *shape) {
circle_t *c = (circle_t *)shape;
printf("Drawing a circle at (%d, %d) with radius %d.\n", c->x, c->y, c->r);
}
void rectangle_draw(shape_t *shape) {
rectangle_t *r = (rectangle_t *)shape;
printf("Drawing a rectangle at (%d, %d) with width %d and height %d.\n", r->x, r->y, r->w, r->h);
}
int main()
{
circle_t circle = { .shape = { circle_draw }, .x = 10, .y = 20, .r = 5 };
rectangle_t rect = { .shape = { rectangle_draw }, .x = 30, .y = 40, .w = 15, .h = 20 };
shape_t *shapes[] = { (shape_t *)&circle, (shape_t *)&rect };
for (size_t i = 0; i < 2; i++) {
shapes[i]->draw(shapes[i]);
}
return 0;
}
This technique is heavily used in C-based GUI toolkits, game engines, and embedded systems frameworks.
Conclusion #
C function pointers unlock a wide range of advanced programming techniques:
- Callbacks for event-driven systems
- Behavior parameterization for reusable functions
- Sorting flexibility via custom comparators
- Dispatch tables for interpreters or state machines
- Recursive backtracking with callback hooks
- Polymorphism in non-OOP environments
Mastering these patterns helps you write more flexible, reusable, and extensible C code, making function pointers a cornerstone of advanced C programming.