Function pointers in C are far more powerful than simple callback mechanisms. Advanced usage of function pointers enables sophisticated programming paradigms: dynamic dispatch, plugin architectures, state machines, closures (simulated), virtual tables, and even basic forms of object-oriented programming. For experienced C developers, mastering these advanced techniques unlocks the ability to write highly flexible, extensible, and maintainable code.
What Makes Function Pointers Advanced?
Advanced function pointer usage goes beyond simple callbacks to include:
- Arrays of function pointers for dispatch tables
- Function pointers as structure members for polymorphic behavior
- Returning function pointers from functions (function factories)
- Pointer to function pointers (double indirection)
- Generic algorithms using function pointers
- State machines with function pointer transitions
- Closures and captured context using
void*parameters
Dispatch Tables: Replacing Switch Statements
#include <stdio.h>
#include <string.h>
#include <ctype.h>
// ============================================================
// DISPATCH TABLES - REPLACING LONG SWITCH STATEMENTS
// ============================================================
// Command handler functions
typedef struct {
char *command;
void (*handler)(char *args);
char *help;
} Command;
// Handler implementations
void cmd_help(char *args) {
printf("Available commands:\n");
// Help text would be here
}
void cmd_exit(char *args) {
printf("Goodbye!\n");
exit(0);
}
void cmd_echo(char *args) {
if (args) printf("%s\n", args);
}
void cmd_calc(char *args) {
// Simple calculator parsing
int a, b;
char op;
if (sscanf(args, "%d %c %d", &a, &op, &b) == 3) {
switch(op) {
case '+': printf("= %d\n", a + b); break;
case '-': printf("= %d\n", a - b); break;
case '*': printf("= %d\n", a * b); break;
case '/':
if (b != 0) printf("= %d\n", a / b);
else printf("Division by zero\n");
break;
default: printf("Unknown operator\n");
}
} else {
printf("Usage: calc <num> <op> <num>\n");
}
}
// Command dispatch table
Command commands[] = {
{"help", cmd_help, "Show this help"},
{"exit", cmd_exit, "Exit the program"},
{"echo", cmd_echo, "Echo arguments"},
{"calc", cmd_calc, "Simple calculator"},
{NULL, NULL, NULL} // Sentinel
};
// Fast lookup using hash table (for larger command sets)
#define HASH_SIZE 256
typedef struct CommandNode {
char *command;
void (*handler)(char *);
struct CommandNode *next;
} CommandNode;
CommandNode *hashTable[HASH_SIZE];
unsigned int hash(const char *str) {
unsigned int hash = 5381;
int c;
while ((c = *str++)) {
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
}
return hash % HASH_SIZE;
}
void registerCommand(const char *cmd, void (*handler)(char *)) {
unsigned int h = hash(cmd);
CommandNode *node = malloc(sizeof(CommandNode));
node->command = strdup(cmd);
node->handler = handler;
node->next = hashTable[h];
hashTable[h] = node;
}
void (*getHandler(const char *cmd))(char *) {
unsigned int h = hash(cmd);
CommandNode *node = hashTable[h];
while (node) {
if (strcmp(node->command, cmd) == 0) {
return node->handler;
}
node = node->next;
}
return NULL;
}
int main() {
printf("=== Dispatch Table Example ===\n");
// Linear search through command table
char input[100];
char cmd[50];
char *args;
while (1) {
printf("\n> ");
fgets(input, sizeof(input), stdin);
input[strcspn(input, "\n")] = 0;
// Parse command and arguments
char *space = strchr(input, ' ');
if (space) {
*space = '\0';
strcpy(cmd, input);
args = space + 1;
} else {
strcpy(cmd, input);
args = NULL;
}
// Look up command in table
int found = 0;
for (int i = 0; commands[i].command != NULL; i++) {
if (strcmp(commands[i].command, cmd) == 0) {
commands[i].handler(args);
found = 1;
break;
}
}
if (!found) {
printf("Unknown command: %s\n", cmd);
}
}
return 0;
}
Polymorphism and Virtual Tables (VTable)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
// ============================================================
// POLYMORPHISM WITH VIRTUAL TABLES (C's version of C++ vtables)
// ============================================================
// Forward declarations
typedef struct Shape Shape;
typedef struct ShapeVTable ShapeVTable;
// Virtual table structure - contains function pointers for polymorphic behavior
struct ShapeVTable {
double (*area)(const Shape*);
double (*perimeter)(const Shape*);
void (*draw)(const Shape*);
void (*destroy)(Shape*);
};
// Base Shape structure - must be first in all derived structs
struct Shape {
ShapeVTable *vtable;
char name[32];
};
// Generic functions that work for any Shape
double shape_area(const Shape *shape) {
return shape->vtable->area(shape);
}
double shape_perimeter(const Shape *shape) {
return shape->vtable->perimeter(shape);
}
void shape_draw(const Shape *shape) {
shape->vtable->draw(shape);
}
void shape_destroy(Shape *shape) {
if (shape && shape->vtable && shape->vtable->destroy) {
shape->vtable->destroy(shape);
} else if (shape) {
free(shape);
}
}
// ==================== CIRCLE ====================
typedef struct {
Shape base; // Base struct must be first
double radius;
} Circle;
// Circle's implementations of virtual functions
double circle_area(const Shape *shape) {
const Circle *circle = (const Circle*)shape;
return M_PI * circle->radius * circle->radius;
}
double circle_perimeter(const Shape *shape) {
const Circle *circle = (const Circle*)shape;
return 2 * M_PI * circle->radius;
}
void circle_draw(const Shape *shape) {
const Circle *circle = (const Circle*)shape;
printf("Drawing Circle '%s' with radius %.2f\n",
shape->name, circle->radius);
}
void circle_destroy(Shape *shape) {
printf("Destroying Circle '%s'\n", shape->name);
free(shape);
}
// Circle's virtual table
ShapeVTable circle_vtable = {
.area = circle_area,
.perimeter = circle_perimeter,
.draw = circle_draw,
.destroy = circle_destroy
};
// Constructor
Circle* circle_create(const char *name, double radius) {
Circle *circle = malloc(sizeof(Circle));
circle->base.vtable = &circle_vtable;
strncpy(circle->base.name, name, 31);
circle->base.name[31] = '\0';
circle->radius = radius;
return circle;
}
// ==================== RECTANGLE ====================
typedef struct {
Shape base;
double width;
double height;
} Rectangle;
// Rectangle's implementations
double rectangle_area(const Shape *shape) {
const Rectangle *rect = (const Rectangle*)shape;
return rect->width * rect->height;
}
double rectangle_perimeter(const Shape *shape) {
const Rectangle *rect = (const Rectangle*)shape;
return 2 * (rect->width + rect->height);
}
void rectangle_draw(const Shape *shape) {
const Rectangle *rect = (const Rectangle*)shape;
printf("Drawing Rectangle '%s' (%.2f x %.2f)\n",
shape->name, rect->width, rect->height);
}
// Rectangle's virtual table
ShapeVTable rectangle_vtable = {
.area = rectangle_area,
.perimeter = rectangle_perimeter,
.draw = rectangle_draw,
.destroy = circle_destroy // Can reuse same destroy if no special needs
};
Rectangle* rectangle_create(const char *name, double width, double height) {
Rectangle *rect = malloc(sizeof(Rectangle));
rect->base.vtable = &rectangle_vtable;
strncpy(rect->base.name, name, 31);
rect->base.name[31] = '\0';
rect->width = width;
rect->height = height;
return rect;
}
// ==================== TRIANGLE ====================
typedef struct {
Shape base;
double a, b, c; // Three sides
} Triangle;
double triangle_area(const Shape *shape) {
const Triangle *tri = (const Triangle*)shape;
// Heron's formula
double s = (tri->a + tri->b + tri->c) / 2.0;
return sqrt(s * (s - tri->a) * (s - tri->b) * (s - tri->c));
}
double triangle_perimeter(const Shape *shape) {
const Triangle *tri = (const Triangle*)shape;
return tri->a + tri->b + tri->c;
}
void triangle_draw(const Shape *shape) {
const Triangle *tri = (const Triangle*)shape;
printf("Drawing Triangle '%s' with sides %.2f, %.2f, %.2f\n",
shape->name, tri->a, tri->b, tri->c);
}
ShapeVTable triangle_vtable = {
.area = triangle_area,
.perimeter = triangle_perimeter,
.draw = triangle_draw,
.destroy = circle_destroy
};
Triangle* triangle_create(const char *name, double a, double b, double c) {
// Validate triangle inequality
if (a + b <= c || a + c <= b || b + c <= a) {
return NULL; // Invalid triangle
}
Triangle *tri = malloc(sizeof(Triangle));
tri->base.vtable = &triangle_vtable;
strncpy(tri->base.name, name, 31);
tri->base.name[31] = '\0';
tri->a = a;
tri->b = b;
tri->c = c;
return tri;
}
int main() {
printf("=== Polymorphism with Virtual Tables ===\n\n");
// Create an array of different shapes
Shape *shapes[5];
shapes[0] = (Shape*)circle_create("Small Circle", 2.5);
shapes[1] = (Shape*)rectangle_create("Golden Rectangle", 4.0, 6.0);
shapes[2] = (Shape*)triangle_create("Right Triangle", 3.0, 4.0, 5.0);
shapes[3] = (Shape*)circle_create("Large Circle", 5.0);
shapes[4] = (Shape*)rectangle_create("Square", 3.0, 3.0);
// Polymorphic processing - same code works for all shapes
printf("Processing shapes polymorphically:\n");
for (int i = 0; i < 5; i++) {
shape_draw(shapes[i]);
printf(" Area: %.2f, Perimeter: %.2f\n\n",
shape_area(shapes[i]),
shape_perimeter(shapes[i]));
}
// Clean up
for (int i = 0; i < 5; i++) {
shape_destroy(shapes[i]);
}
return 0;
}
Function Factories (Returning Function Pointers)
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
// ============================================================
// FUNCTION FACTORIES - FUNCTIONS THAT RETURN FUNCTION POINTERS
// ============================================================
typedef double (*MathFunc)(double);
// Basic math functions
double square(double x) { return x * x; }
double cube(double x) { return x * x * x; }
double sqrt_func(double x) { return sqrt(x); }
double identity(double x) { return x; }
double reciprocal(double x) { return x != 0 ? 1.0 / x : 0; }
double negate(double x) { return -x; }
// Function factory - returns appropriate function based on operation
MathFunc get_operation(char op) {
switch (op) {
case '^': return square; // Actually square, not power
case '3': return cube;
case 'r': return sqrt_func;
case 'i': return identity;
case '/': return reciprocal;
case '-': return negate;
default: return NULL;
}
}
// Function factory with parameters - returns a configured function
typedef struct {
double a; // multiplier
double b; // addend
} LinearParams;
double linear_func(double x, void *params) {
LinearParams *p = (LinearParams*)params;
return p->a * x + p->b;
}
// Factory that creates a closure-like function
MathFunc create_linear(double a, double b) {
// Allocate persistent parameters
LinearParams *params = malloc(sizeof(LinearParams));
params->a = a;
params->b = b;
// We need a wrapper that captures the parameters
// This is tricky in C - we'll use a global approach or pass params separately
// For now, we'll demonstrate a different approach
// Alternative: Return a function that uses global/static state
// (Not shown here for simplicity)
printf("Created linear function: %.2f*x + %.2f\n", a, b);
return NULL; // Placeholder
}
// More practical: Function that returns a function based on string
typedef enum { MATH_SIN, MATH_COS, MATH_TAN, MATH_LOG, MATH_EXP } MathFuncType;
MathFunc get_math_function(MathFuncType type) {
switch (type) {
case MATH_SIN: return sin;
case MATH_COS: return cos;
case MATH_TAN: return tan;
case MATH_LOG: return log;
case MATH_EXP: return exp;
default: return NULL;
}
}
// Derivative approximator - returns function that approximates derivative
MathFunc derivative(MathFunc f, double h) {
// This would need to return a dynamically created function
// In C, we need a wrapper function that uses a global/static context
// This is a limitation - closures aren't directly supported
// For demonstration, we'll create a static wrapper
printf("Created derivative approximator with step %.6f\n", h);
// In real code, you'd need a struct with function pointer + context
return NULL;
}
// Better approach: Function + context struct
typedef struct {
MathFunc func;
void *context;
} FuncWithContext;
double call_with_context(FuncWithContext *fc, double x) {
// This would call fc->func with access to fc->context
return 0; // Simplified
}
int main() {
printf("=== Function Factories ===\n\n");
// Get functions from factory
char ops[] = {'^', '3', 'r', 'i', '/', '-'};
double test_value = 4.0;
for (int i = 0; i < 6; i++) {
MathFunc f = get_operation(ops[i]);
if (f) {
printf("%c(%.2f) = %.4f\n", ops[i], test_value, f(test_value));
}
}
// Get math functions
MathFuncType types[] = {MATH_SIN, MATH_COS, MATH_TAN};
double angle = M_PI / 4; // 45 degrees
for (int i = 0; i < 3; i++) {
MathFunc f = get_math_function(types[i]);
if (f) {
printf("math[%d](%.4f) = %.4f\n", i, angle, f(angle));
}
}
return 0;
}
State Machines with Function Pointers
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
// ============================================================
// ADVANCED STATE MACHINES WITH FUNCTION POINTERS
// ============================================================
// Forward declarations
typedef struct StateMachine StateMachine;
typedef void (*StateFunc)(StateMachine*);
// State machine structure
struct StateMachine {
StateFunc current_state;
char *state_name;
int data;
int timer;
void *context; // For additional data
};
// ==================== TRAFFIC LIGHT EXAMPLE ====================
typedef struct {
int cars_waiting;
int pedestrians_waiting;
int pedestrian_request;
} TrafficContext;
void red_state(StateMachine *sm) {
TrafficContext *ctx = (TrafficContext*)sm->context;
sm->state_name = "RED";
printf("🛑 RED - Cars stop");
if (ctx->pedestrians_waiting) {
printf(" (🚶 Pedestrians crossing)");
}
printf("\n");
sm->timer++;
// Check for pedestrian request
if (ctx->pedestrian_request && sm->timer >= 10) {
sm->current_state = red_state; // Stay in red for pedestrians
ctx->pedestrians_waiting = 1;
ctx->pedestrian_request = 0;
sm->timer = 0;
}
// Normal transition to green
else if (sm->timer >= 30) {
sm->current_state = green_state;
sm->timer = 0;
}
}
void green_state(StateMachine *sm) {
TrafficContext *ctx = (TrafficContext*)sm->context;
sm->state_name = "GREEN";
printf("🟢 GREEN - Cars go (%d cars waiting)\n", ctx->cars_waiting);
sm->timer++;
// Serve waiting cars
if (ctx->cars_waiting > 0 && sm->timer % 5 == 0) {
ctx->cars_waiting--;
printf(" Car served, %d remaining\n", ctx->cars_waiting);
}
// Check for pedestrian or transition to yellow
if (ctx->pedestrians_waiting && sm->timer >= 15) {
sm->current_state = red_state;
ctx->pedestrians_waiting = 0;
sm->timer = 0;
} else if (sm->timer >= 25) {
sm->current_state = yellow_state;
sm->timer = 0;
}
}
void yellow_state(StateMachine *sm) {
sm->state_name = "YELLOW";
printf("🟡 YELLOW - Prepare to stop\n");
sm->timer++;
if (sm->timer >= 5) {
sm->current_state = red_state;
sm->timer = 0;
}
}
// ==================== PARSER STATE MACHINE ====================
typedef enum { TOKEN_NONE, TOKEN_NUMBER, TOKEN_OP, TOKEN_END } TokenType;
typedef struct {
char *input;
int pos;
double result;
char last_op;
double current_number;
} ParserContext;
void parser_start(StateMachine *sm);
void parser_number(StateMachine *sm);
void parser_operator(StateMachine *sm);
void parser_error(StateMachine *sm);
void parser_end(StateMachine *sm);
void parser_start(StateMachine *sm) {
ParserContext *ctx = (ParserContext*)sm->context;
sm->state_name = "START";
// Skip whitespace
while (ctx->input[ctx->pos] == ' ') ctx->pos++;
if (ctx->input[ctx->pos] >= '0' && ctx->input[ctx->pos] <= '9') {
sm->current_state = parser_number;
} else {
sm->current_state = parser_error;
}
}
void parser_number(StateMachine *sm) {
ParserContext *ctx = (ParserContext*)sm->context;
sm->state_name = "NUMBER";
// Parse number
char *endptr;
ctx->current_number = strtod(ctx->input + ctx->pos, &endptr);
ctx->pos = endptr - ctx->input;
// Apply operation if we have one pending
if (ctx->last_op) {
switch (ctx->last_op) {
case '+': ctx->result += ctx->current_number; break;
case '-': ctx->result -= ctx->current_number; break;
case '*': ctx->result *= ctx->current_number; break;
case '/':
if (ctx->current_number != 0)
ctx->result /= ctx->current_number;
break;
}
ctx->last_op = 0;
} else {
ctx->result = ctx->current_number;
}
// Skip whitespace
while (ctx->input[ctx->pos] == ' ') ctx->pos++;
// Check next character
char c = ctx->input[ctx->pos];
if (c == '+' || c == '-' || c == '*' || c == '/') {
ctx->last_op = c;
ctx->pos++;
sm->current_state = parser_operator;
} else if (c == '\0') {
sm->current_state = parser_end;
} else {
sm->current_state = parser_error;
}
}
void parser_operator(StateMachine *sm) {
ParserContext *ctx = (ParserContext*)sm->context;
sm->state_name = "OPERATOR";
// Skip whitespace
while (ctx->input[ctx->pos] == ' ') ctx->pos++;
if (ctx->input[ctx->pos] >= '0' && ctx->input[ctx->pos] <= '9') {
sm->current_state = parser_number;
} else if (ctx->input[ctx->pos] == '\0') {
sm->current_state = parser_error; // Expression can't end with operator
} else {
sm->current_state = parser_error;
}
}
void parser_error(StateMachine *sm) {
ParserContext *ctx = (ParserContext*)sm->context;
sm->state_name = "ERROR";
printf("Parse error at position %d: '%s'\n",
ctx->pos, ctx->input + ctx->pos);
ctx->result = 0;
}
void parser_end(StateMachine *sm) {
sm->state_name = "END";
printf("Parse complete. Result = %.2f\n",
((ParserContext*)sm->context)->result);
}
// Generic state machine runner
void run_state_machine(StateMachine *sm, int max_steps) {
int step = 0;
while (sm->current_state != NULL && step < max_steps) {
sm->current_state(sm);
step++;
}
}
int main() {
printf("=== Advanced State Machines ===\n\n");
// Traffic light example
printf("--- Traffic Light ---\n");
TrafficContext traffic_ctx = {
.cars_waiting = 5,
.pedestrians_waiting = 0,
.pedestrian_request = 0
};
StateMachine traffic_lights = {
.current_state = red_state,
.state_name = "RED",
.timer = 0,
.context = &traffic_ctx
};
// Simulate 40 seconds of traffic light operation
for (int i = 0; i < 40; i++) {
printf("t=%2d: ", i);
traffic_lights.current_state(&traffic_lights);
// Random events
if (rand() % 10 == 0) {
traffic_ctx.cars_waiting++;
printf(" (🚗 New car arrived, now %d waiting)\n",
traffic_ctx.cars_waiting);
}
if (rand() % 15 == 0) {
traffic_ctx.pedestrian_request = 1;
printf(" (🚶 Pedestrian waiting to cross)\n");
}
}
// Parser example
printf("\n--- Expression Parser ---\n");
char expression[] = "10 + 20 * 3 - 5 / 2";
ParserContext parser_ctx = {
.input = expression,
.pos = 0,
.result = 0,
.last_op = 0,
.current_number = 0
};
StateMachine parser = {
.current_state = parser_start,
.state_name = "START",
.context = &parser_ctx
};
run_state_machine(&parser, 20);
return 0;
}
Generic Algorithms with Function Pointers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// GENERIC ALGORITHMS USING FUNCTION POINTERS
// ============================================================
typedef int (*Comparator)(const void*, const void*);
typedef void (*Printer)(const void*);
typedef void (*Transformer)(void*);
typedef int (*Predicate)(const void*);
// Generic find if
void* find_if(void *base, size_t count, size_t size,
Predicate pred, void *context) {
char *ptr = (char*)base;
for (size_t i = 0; i < count; i++) {
if (pred(ptr + i * size)) {
return ptr + i * size;
}
}
return NULL;
}
// Generic for each
void for_each(void *base, size_t count, size_t size, Transformer trans) {
char *ptr = (char*)base;
for (size_t i = 0; i < count; i++) {
trans(ptr + i * size);
}
}
// Generic accumulate
double accumulate(const void *base, size_t count, size_t size,
double (*func)(const void*)) {
double sum = 0;
const char *ptr = (const char*)base;
for (size_t i = 0; i < count; i++) {
sum += func(ptr + i * size);
}
return sum;
}
// Generic filter (creates new array)
void* filter(const void *base, size_t count, size_t size,
Predicate pred, size_t *result_count) {
// First pass - count matches
size_t matches = 0;
const char *src = (const char*)base;
for (size_t i = 0; i < count; i++) {
if (pred(src + i * size)) {
matches++;
}
}
if (matches == 0) {
*result_count = 0;
return NULL;
}
// Allocate result array
void *result = malloc(matches * size);
char *dst = (char*)result;
// Copy matches
for (size_t i = 0; i < count; i++) {
if (pred(src + i * size)) {
memcpy(dst, src + i * size, size);
dst += size;
}
}
*result_count = matches;
return result;
}
// ==================== INTEGER SPECIFIC IMPLEMENTATIONS ====================
int int_compare(const void *a, const void *b) {
return *(const int*)a - *(const int*)b;
}
void int_print(const void *elem) {
printf("%d", *(const int*)elem);
}
void int_double(void *elem) {
*(int*)elem *= 2;
}
int int_is_even(const void *elem) {
return *(const int*)elem % 2 == 0;
}
double int_to_double(const void *elem) {
return (double)*(const int*)elem;
}
// ==================== STRING SPECIFIC IMPLEMENTATIONS ====================
int string_compare(const void *a, const void *b) {
return strcmp(*(const char**)a, *(const char**)b);
}
void string_print(const void *elem) {
printf("\"%s\"", *(const char**)elem);
}
void string_uppercase(void *elem) {
char *str = *(char**)elem;
for (; *str; str++) {
*str = toupper(*str);
}
}
int string_length_gt(const void *elem, int len) {
return strlen(*(const char**)elem) > len;
}
// Wrapper for predicate with context
typedef struct {
Predicate pred;
void *context;
} PredicateWithContext;
int predicate_wrapper(const void *elem, void *ctx) {
PredicateWithContext *pwc = (PredicateWithContext*)ctx;
return pwc->pred(elem);
}
// ==================== PERSON STRUCTURE ====================
typedef struct {
char name[50];
int age;
double salary;
} Person;
void person_print(const void *elem) {
const Person *p = (const Person*)elem;
printf("%s (%d) $%.2f", p->name, p->age, p->salary);
}
int person_age_gt_30(const void *elem) {
return ((const Person*)elem)->age > 30;
}
double person_salary_to_double(const void *elem) {
return ((const Person*)elem)->salary;
}
int person_compare_by_age(const void *a, const void *b) {
return ((const Person*)a)->age - ((const Person*)b)->age;
}
int person_compare_by_name(const void *a, const void *b) {
return strcmp(((const Person*)a)->name, ((const Person*)b)->name);
}
int main() {
printf("=== Generic Algorithms ===\n\n");
// Integer examples
printf("--- Integer Operations ---\n");
int numbers[] = {5, 12, 7, 8, 3, 10, 15, 6, 9, 4};
size_t num_count = sizeof(numbers) / sizeof(numbers[0]);
printf("Original: ");
for_each(numbers, num_count, sizeof(int), int_print);
printf("\n");
// Find first even number
int *first_even = (int*)find_if(numbers, num_count, sizeof(int),
int_is_even, NULL);
if (first_even) {
printf("First even number: %d\n", *first_even);
}
// Double all numbers
for_each(numbers, num_count, sizeof(int), int_double);
printf("After doubling: ");
for_each(numbers, num_count, sizeof(int), int_print);
printf("\n");
// Sum all numbers
double sum = accumulate(numbers, num_count, sizeof(int), int_to_double);
printf("Sum: %.0f\n", sum);
// Filter even numbers
size_t even_count;
int *evens = (int*)filter(numbers, num_count, sizeof(int),
int_is_even, &even_count);
if (evens) {
printf("Even numbers: ");
for (size_t i = 0; i < even_count; i++) {
printf("%d ", evens[i]);
}
printf("\n");
free(evens);
}
// String examples
printf("\n--- String Operations ---\n");
char *fruits[] = {"apple", "banana", "cherry", "date", "elderberry"};
size_t fruit_count = sizeof(fruits) / sizeof(fruits[0]);
printf("Original strings: ");
for_each(fruits, fruit_count, sizeof(char*), string_print);
printf("\n");
// Make uppercase
for_each(fruits, fruit_count, sizeof(char*), string_uppercase);
printf("Uppercase: ");
for_each(fruits, fruit_count, sizeof(char*), string_print);
printf("\n");
// Person examples
printf("\n--- Person Operations ---\n");
Person people[] = {
{"Alice", 25, 65000},
{"Bob", 35, 82000},
{"Charlie", 42, 95000},
{"Diana", 31, 71000},
{"Eve", 28, 58000}
};
size_t person_count = sizeof(people) / sizeof(people[0]);
printf("People:\n");
for (size_t i = 0; i < person_count; i++) {
printf(" ");
person_print(&people[i]);
printf("\n");
}
// Filter people over 30
size_t over30_count;
Person *over30 = (Person*)filter(people, person_count, sizeof(Person),
person_age_gt_30, &over30_count);
if (over30) {
printf("\nPeople over 30:\n");
for (size_t i = 0; i < over30_count; i++) {
printf(" ");
person_print(&over30[i]);
printf("\n");
}
free(over30);
}
// Total salary
double total_salary = accumulate(people, person_count, sizeof(Person),
person_salary_to_double);
printf("\nTotal salary: $%.2f\n", total_salary);
printf("Average salary: $%.2f\n", total_salary / person_count);
return 0;
}
Closures and Captured Context
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// SIMULATING CLOSURES WITH CONTEXT STRUCTURES
// ============================================================
// Generic function with context
typedef struct {
void (*func)(void*, void*); // Function that takes context and data
void *context; // Captured variables
} Closure;
void closure_call(Closure *c, void *data) {
c->func(c->context, data);
}
// ==================== COUNTER CLOSURE ====================
typedef struct {
int count;
int step;
} CounterContext;
void counter_func(void *ctx, void *data) {
CounterContext *c = (CounterContext*)ctx;
int *output = (int*)data;
c->count += c->step;
*output = c->count;
}
Closure* create_counter(int initial, int step) {
CounterContext *ctx = malloc(sizeof(CounterContext));
ctx->count = initial;
ctx->step = step;
Closure *c = malloc(sizeof(Closure));
c->func = counter_func;
c->context = ctx;
return c;
}
// ==================== MULTIPLIER CLOSURE ====================
typedef struct {
double factor;
} MultiplierContext;
void multiplier_func(void *ctx, void *data) {
MultiplierContext *m = (MultiplierContext*)ctx;
double *value = (double*)data;
*value *= m->factor;
}
Closure* create_multiplier(double factor) {
MultiplierContext *ctx = malloc(sizeof(MultiplierContext));
ctx->factor = factor;
Closure *c = malloc(sizeof(Closure));
c->func = multiplier_func;
c->context = ctx;
return c;
}
// ==================== FILTER PREDICATE WITH CONTEXT ====================
typedef struct {
int (*pred)(void*, void*); // Predicate that takes context and element
void *context;
} PredicateClosure;
int predicate_closure_call(PredicateClosure *pc, void *elem) {
return pc->pred(pc->context, elem);
}
// Example: greater than threshold
typedef struct {
double threshold;
} GreaterThanContext;
int greater_than_func(void *ctx, void *elem) {
GreaterThanContext *g = (GreaterThanContext*)ctx;
double *value = (double*)elem;
return *value > g->threshold;
}
PredicateClosure* create_greater_than(double threshold) {
GreaterThanContext *ctx = malloc(sizeof(GreaterThanContext));
ctx->threshold = threshold;
PredicateClosure *pc = malloc(sizeof(PredicateClosure));
pc->pred = greater_than_func;
pc->context = ctx;
return pc;
}
// ==================== MAP FUNCTION WITH CONTEXT ====================
typedef struct {
double (*map)(void*, void*);
void *context;
} MapClosure;
double map_closure_call(MapClosure *mc, void *elem) {
return mc->map(mc->context, elem);
}
// Example: add constant
typedef struct {
double constant;
} AddConstantContext;
double add_constant_func(void *ctx, void *elem) {
AddConstantContext *a = (AddConstantContext*)ctx;
double *value = (double*)elem;
return *value + a->constant;
}
MapClosure* create_add_constant(double constant) {
AddConstantContext *ctx = malloc(sizeof(AddConstantContext));
ctx->constant = constant;
MapClosure *mc = malloc(sizeof(MapClosure));
mc->map = add_constant_func;
mc->context = ctx;
return mc;
}
int main() {
printf("=== Simulating Closures ===\n\n");
// Counter closure
Closure *counter = create_counter(0, 2);
printf("Counter closure (step 2):\n");
for (int i = 0; i < 5; i++) {
int value;
closure_call(counter, &value);
printf(" Call %d: %d\n", i + 1, value);
}
// Multiplier closure
Closure *multiplier = create_multiplier(1.5);
double num = 10.0;
printf("\nMultiplier closure (factor 1.5):\n");
for (int i = 0; i < 3; i++) {
closure_call(multiplier, &num);
printf(" After call %d: %.2f\n", i + 1, num);
}
// Filter with context
double numbers[] = {1.5, 3.7, 2.1, 5.8, 0.9, 4.2};
size_t count = sizeof(numbers) / sizeof(numbers[0]);
PredicateClosure *greater_than = create_greater_than(3.0);
printf("\nNumbers greater than 3.0:\n");
for (size_t i = 0; i < count; i++) {
if (predicate_closure_call(greater_than, &numbers[i])) {
printf(" %.2f\n", numbers[i]);
}
}
// Map with context
MapClosure *add_five = create_add_constant(5.0);
printf("\nOriginal numbers + 5:\n");
for (size_t i = 0; i < count; i++) {
double result = map_closure_call(add_five, &numbers[i]);
printf(" %.2f → %.2f\n", numbers[i], result);
}
// Clean up
free(counter->context);
free(counter);
free(multiplier->context);
free(multiplier);
free(greater_than->context);
free(greater_than);
free(add_five->context);
free(add_five);
return 0;
}
Pointer to Function Pointers (Double Indirection)
#include <stdio.h>
#include <stdlib.h>
// ============================================================
// POINTER TO FUNCTION POINTERS (DOUBLE INDIRECTION)
// ============================================================
typedef int (*Operation)(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; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }
// Function that modifies a function pointer
void set_operation(Operation *op_ptr, char op) {
switch (op) {
case '+': *op_ptr = add; break;
case '-': *op_ptr = subtract; break;
case '*': *op_ptr = multiply; break;
case '/': *op_ptr = divide; break;
default: *op_ptr = NULL;
}
}
// Array of function pointers
Operation operations[4] = {add, subtract, multiply, divide};
// Pointer to array of function pointers
Operation (*operations_ptr)[4] = &operations;
// Function that returns a pointer to a function pointer
Operation* get_operation_ptr(int index) {
if (index >= 0 && index < 4) {
return &operations[index];
}
return NULL;
}
// Dynamic dispatch with function pointer table
typedef struct {
Operation *vtable; // Pointer to array of function pointers
int data;
} DynamicObject;
int main() {
printf("=== Pointer to Function Pointers ===\n\n");
Operation current_op = NULL;
// Set function pointer through pointer to pointer
printf("Setting operations through pointer:\n");
set_operation(¤t_op, '+');
printf(" 10 + 5 = %d\n", current_op(10, 5));
set_operation(¤t_op, '*');
printf(" 10 * 5 = %d\n", current_op(10, 5));
// Access array of function pointers
printf("\nAccessing array of function pointers:\n");
for (int i = 0; i < 4; i++) {
printf(" operations[%d] at %p\n", i, (void*)operations[i]);
}
// Get pointer to a specific function pointer
Operation *op_ptr = get_operation_ptr(2); // multiply
if (op_ptr) {
printf("\nGot pointer to operation[2]: %p\n", (void*)*op_ptr);
printf(" 7 * 6 = %d\n", (*op_ptr)(7, 6));
}
// Modify through pointer
op_ptr = get_operation_ptr(1); // subtract
printf("\nBefore change: %d - %d = %d\n", 10, 3, (*op_ptr)(10, 3));
// Change the function pointer through the pointer
*op_ptr = add;
printf("After change: %d + %d = %d\n", 10, 3, (*op_ptr)(10, 3));
// Dynamic object with vtable pointer
printf("\nDynamic object with vtable:\n");
DynamicObject obj;
obj.vtable = operations;
obj.data = 42;
Operation *vtable = obj.vtable;
printf(" obj.data = %d\n", obj.data);
printf(" vtable[0] (add): %d\n", vtable[0](10, 5));
printf(" vtable[1] (subtract): %d\n", vtable[1](10, 5));
return 0;
}
Function Pointer Performance Optimization
#include <stdio.h>
#include <time.h>
// ============================================================
// PERFORMANCE OPTIMIZATION WITH FUNCTION POINTERS
// ============================================================
// Direct call vs indirect call performance comparison
typedef int (*MathFunc)(int);
int square(int x) { return x * x; }
int cube(int x) { return x * x * x; }
int identity(int x) { return x; }
// Inline function (compiler may inline)
static inline int inline_square(int x) { return x * x; }
// Function that uses direct calls
int direct_sum(int *arr, int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += square(arr[i]); // Direct call
}
return sum;
}
// Function that uses indirect calls (function pointer)
int indirect_sum(int *arr, int n, MathFunc f) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += f(arr[i]); // Indirect call through pointer
}
return sum;
}
// Function that uses branch prediction friendly dispatch
typedef enum { OP_SQUARE, OP_CUBE, OP_IDENTITY } OpType;
int dispatch_sum(int *arr, int n, OpType op) {
int sum = 0;
for (int i = 0; i < n; i++) {
switch (op) {
case OP_SQUARE: sum += square(arr[i]); break;
case OP_CUBE: sum += cube(arr[i]); break;
case OP_IDENTITY: sum += identity(arr[i]); break;
}
}
return sum;
}
// Function pointer table for computed goto style (using function pointers)
MathFunc op_table[] = {square, cube, identity};
int table_sum(int *arr, int n, int op_index) {
int sum = 0;
MathFunc f = op_table[op_index];
for (int i = 0; i < n; i++) {
sum += f(arr[i]);
}
return sum;
}
int main() {
printf("=== Performance Optimization ===\n\n");
const int SIZE = 10000000;
int *arr = malloc(SIZE * sizeof(int));
for (int i = 0; i < SIZE; i++) {
arr[i] = i % 100;
}
clock_t start, end;
// Direct call
start = clock();
int result1 = direct_sum(arr, SIZE);
end = clock();
printf("Direct call: %.3f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);
// Indirect call (function pointer)
start = clock();
int result2 = indirect_sum(arr, SIZE, square);
end = clock();
printf("Indirect call: %.3f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);
// Dispatch (switch statement)
start = clock();
int result3 = dispatch_sum(arr, SIZE, OP_SQUARE);
end = clock();
printf("Dispatch switch: %.3f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);
// Table lookup
start = clock();
int result4 = table_sum(arr, SIZE, 0); // 0 = square
end = clock();
printf("Table dispatch: %.3f seconds\n",
(double)(end - start) / CLOCKS_PER_SEC);
printf("\nAll results: %d, %d, %d, %d\n",
result1, result2, result3, result4);
printf("\nPerformance notes:\n");
printf(" - Direct calls are fastest (can be inlined)\n");
printf(" - Indirect calls have small overhead (branch prediction)\n");
printf(" - Modern CPUs handle both well\n");
printf(" - Table dispatch can be as fast as direct for stable prediction\n");
free(arr);
return 0;
}
Best Practices for Advanced Function Pointers
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// ============================================================
// BEST PRACTICES FOR ADVANCED FUNCTION POINTER USAGE
// ============================================================
// 1. Always check for NULL before calling
#define SAFE_CALL(func, ...) \
do { \
if (func != NULL) { \
func(__VA_ARGS__); \
} \
} while(0)
// 2. Use typedefs for complex function pointer types
typedef int (*Comparator)(const void*, const void*);
typedef void (*ErrorHandler)(const char*);
typedef void* (*Allocator)(size_t);
// 3. Document function pointer expectations
/**
* Function pointer type for comparison operations
* @param a First element to compare
* @param b Second element to compare
* @return negative if a<b, 0 if a==b, positive if a>b
*/
typedef int (*CompareFunc)(const void*, const void*);
// 4. Use const for function pointers that shouldn't change
void process_array(const int *arr, size_t size,
const CompareFunc cmp) {
// cmp cannot be reassigned inside function
}
// 5. Encapsulate function pointers with their context
typedef struct {
CompareFunc compare;
void *context;
} CompareWithContext;
int compare_with_context(const void *a, const void *b,
CompareWithContext *ctx) {
// Use ctx->compare with access to ctx->context
return 0;
}
// 6. Provide default implementations
void default_error_handler(const char *msg) {
fprintf(stderr, "Error: %s\n", msg);
}
typedef struct {
ErrorHandler error_handler;
Allocator allocator;
// ... other function pointers
} LibraryConfig;
LibraryConfig library_config = {
.error_handler = default_error_handler,
.allocator = malloc
};
// 7. Validate function pointers in debug builds
void validate_comparator(CompareFunc cmp) {
assert(cmp != NULL);
// Additional validation in debug mode
int test = cmp(&test, &test);
assert(test == 0); // Should return 0 for equal elements
}
// 8. Function pointer arrays with bounds checking
#define MAX_HANDLERS 10
typedef struct {
int event_type;
void (*handler)(void*);
void *data;
} EventHandler;
EventHandler handlers[MAX_HANDLERS];
int handler_count = 0;
int register_handler(int event_type, void (*handler)(void*), void *data) {
if (handler_count >= MAX_HANDLERS) return -1;
handlers[handler_count].event_type = event_type;
handlers[handler_count].handler = handler;
handlers[handler_count].data = data;
handler_count++;
return 0;
}
void dispatch_event(int event_type) {
for (int i = 0; i < handler_count; i++) {
if (handlers[i].event_type == event_type &&
handlers[i].handler != NULL) {
handlers[i].handler(handlers[i].data);
}
}
}
// 9. Thread safety considerations
#include <pthread.h>
typedef struct {
void (*func)(void*);
void *arg;
pthread_mutex_t lock;
} ThreadSafeCallback;
void ts_callback_call(ThreadSafeCallback *cb) {
pthread_mutex_lock(&cb->lock);
if (cb->func) {
cb->func(cb->arg);
}
pthread_mutex_unlock(&cb->lock);
}
// 10. Function pointer versioning
typedef struct {
int version;
void (*v1_func)(void);
void (*v2_func)(int);
// Newer versions add more function pointers
} VersionedAPI;
void call_versioned_api(VersionedAPI *api) {
if (api->version >= 2 && api->v2_func) {
api->v2_func(42);
} else if (api->v1_func) {
api->v1_func();
}
}
int main() {
printf("=== Best Practices ===\n\n");
// Using SAFE_CALL macro
ErrorHandler handler = NULL;
SAFE_CALL(handler, "This won't crash");
handler = default_error_handler;
SAFE_CALL(handler, "This will print");
// Event dispatch example
register_handler(1, (void(*)(void*))printf, "Event 1 occurred\n");
register_handler(2, (void(*)(void*))printf, "Event 2 occurred\n");
dispatch_event(1);
dispatch_event(2);
dispatch_event(3); // No handler
return 0;
}
Conclusion
Advanced function pointer techniques in C enable sophisticated programming paradigms that rival those found in higher-level languages. Key takeaways:
- Virtual Tables provide polymorphism and object-oriented behavior
- Dispatch Tables replace large switch statements for cleaner code
- Function Factories enable dynamic function creation
- State Machines become elegant with function pointer transitions
- Generic Algorithms work with any data type using function pointers
- Closures can be simulated with context structures
- Double Indirection allows modifying function pointers dynamically
- Performance considerations help optimize critical paths
Mastering these advanced techniques transforms C programming from simple procedural code to flexible, maintainable, and extensible systems that can adapt to changing requirements without sacrificing performance.