Function Pointers Advanced C: Mastering Dynamic Dispatch, Callbacks, and Polymorphism

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:

  1. Arrays of function pointers for dispatch tables
  2. Function pointers as structure members for polymorphic behavior
  3. Returning function pointers from functions (function factories)
  4. Pointer to function pointers (double indirection)
  5. Generic algorithms using function pointers
  6. State machines with function pointer transitions
  7. 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(&current_op, '+');
printf("  10 + 5 = %d\n", current_op(10, 5));
set_operation(&current_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:

  1. Virtual Tables provide polymorphism and object-oriented behavior
  2. Dispatch Tables replace large switch statements for cleaner code
  3. Function Factories enable dynamic function creation
  4. State Machines become elegant with function pointer transitions
  5. Generic Algorithms work with any data type using function pointers
  6. Closures can be simulated with context structures
  7. Double Indirection allows modifying function pointers dynamically
  8. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper