Docs
README
Function Pointers in C
Table of Contents
- •Introduction
- •Basic Concepts
- •Declaring Function Pointers
- •Using Function Pointers
- •Function Pointers as Parameters
- •Callback Functions
- •Arrays of Function Pointers
- •Function Pointers in Structures
- •Typedef with Function Pointers
- •Generic Programming
- •State Machines
- •Plugin Architecture
- •Common Patterns
- •Pitfalls and Best Practices
- •Summary
Introduction
A function pointer is a pointer that points to the address of a function rather than a data value. Function pointers enable:
- •Callback mechanisms: Pass functions as arguments to other functions
- •Dynamic function dispatch: Select which function to call at runtime
- •Event handling: Register handlers for various events
- •Plugin systems: Load and execute code dynamically
- •State machines: Implement clean state transitions
- •Generic algorithms: Create reusable sorting, searching, etc.
- •Object-oriented patterns: Implement virtual methods and polymorphism
Function pointers are fundamental to many C libraries and frameworks, including:
- •qsort() and bsearch() in the standard library
- •Signal handlers
- •Thread creation with pthread
- •GUI event callbacks
Basic Concepts
Functions Have Addresses
Like variables, functions are stored in memory and have addresses:
#include <stdio.h>
void greet(void) {
printf("Hello, World!\n");
}
int main(void) {
// Print the address of the function
printf("Address of greet: %p\n", (void*)greet);
printf("Address of main: %p\n", (void*)main);
return 0;
}
Function Name as Pointer
The function name without parentheses is a pointer to the function:
void foo(void) { }
// These are equivalent:
void (*ptr1)(void) = foo;
void (*ptr2)(void) = &foo; // & is optional
// Calling through pointer (these are equivalent):
ptr1();
(*ptr1)();
Declaring Function Pointers
Syntax
The declaration syntax mirrors the function signature:
return_type (*pointer_name)(parameter_types);
Examples
// Pointer to function taking no args, returning void
void (*ptr1)(void);
// Pointer to function taking int, returning int
int (*ptr2)(int);
// Pointer to function taking two ints, returning int
int (*ptr3)(int, int);
// Pointer to function taking char*, returning char*
char* (*ptr4)(char*);
// Pointer to function taking void*, returning void
void (*ptr5)(void*);
// Pointer to function taking int, int*, returning double
double (*ptr6)(int, int*);
Reading Function Pointer Declarations
Use the "spiral rule" or work inside-out:
int (*fp)(int, int)
│ └───────── parameters: (int, int)
└───────────── pointer to function
└────────────────── returning int
// So: fp is a pointer to a function that takes two ints and returns an int
Complex Examples
// Pointer to function returning pointer to int
int* (*fp1)(void);
// Pointer to function taking function pointer, returning function pointer
int (*(*fp2)(int (*)(int)))(int);
// Array of function pointers
void (*fp_array[10])(int);
// Pointer to array of function pointers
void (*(*fp_ptr_array)[])(int);
Using Function Pointers
Basic Assignment and Calling
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int main(void) {
// Declare and assign function pointer
int (*operation)(int, int);
// Assign to add function
operation = add;
printf("5 + 3 = %d\n", operation(5, 3));
// Reassign to subtract function
operation = subtract;
printf("5 - 3 = %d\n", operation(5, 3));
return 0;
}
Comparing Function Pointers
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int main(void) {
int (*fp)(int, int) = add;
if (fp == add) {
printf("fp points to add\n");
}
if (fp != multiply) {
printf("fp does not point to multiply\n");
}
// Check for NULL
if (fp != NULL) {
int result = fp(2, 3);
}
return 0;
}
NULL Function Pointers
int (*fp)(int) = NULL;
// Always check before calling
if (fp != NULL) {
int result = fp(42);
}
// Or use the pointer directly in condition
if (fp) {
fp(42);
}
Function Pointers as Parameters
Basic Callback
#include <stdio.h>
// Function that accepts a callback
void process(int data, void (*callback)(int)) {
printf("Processing data: %d\n", data);
// Call the callback with the result
if (callback != NULL) {
callback(data * 2);
}
}
// Callback function
void print_result(int result) {
printf("Result: %d\n", result);
}
void save_result(int result) {
printf("Saving result %d to file...\n", result);
}
int main(void) {
process(10, print_result); // Result: 20
process(10, save_result); // Saving result 20 to file...
process(10, NULL); // No callback
return 0;
}
Generic Comparison Function
#include <stdio.h>
#include <stdlib.h>
// Comparison function type
typedef int (*CompareFunc)(const void*, const void*);
// Find maximum element using comparison function
void* find_max(void *array, size_t count, size_t size, CompareFunc compare) {
if (count == 0) return NULL;
char *base = (char*)array;
void *max = base;
for (size_t i = 1; i < count; i++) {
void *current = base + i * size;
if (compare(current, max) > 0) {
max = current;
}
}
return max;
}
// Compare integers
int compare_int(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
// Compare strings
int compare_str(const void *a, const void *b) {
return strcmp(*(char**)a, *(char**)b);
}
int main(void) {
int numbers[] = {3, 1, 4, 1, 5, 9, 2, 6};
int *max_num = find_max(numbers, 8, sizeof(int), compare_int);
printf("Max number: %d\n", *max_num);
char *names[] = {"Charlie", "Alice", "Bob", "David"};
char **max_name = find_max(names, 4, sizeof(char*), compare_str);
printf("Max name: %s\n", *max_name);
return 0;
}
Callback Functions
Event-Driven Callbacks
#include <stdio.h>
// Event types
typedef enum {
EVENT_START,
EVENT_STOP,
EVENT_ERROR
} EventType;
// Callback type
typedef void (*EventCallback)(EventType event, void *data);
// Event handler structure
typedef struct {
EventCallback callback;
void *user_data;
} EventHandler;
// Global event handlers
#define MAX_HANDLERS 10
static EventHandler handlers[MAX_HANDLERS];
static int handler_count = 0;
// Register callback
void register_handler(EventCallback callback, void *user_data) {
if (handler_count < MAX_HANDLERS) {
handlers[handler_count].callback = callback;
handlers[handler_count].user_data = user_data;
handler_count++;
}
}
// Trigger event
void trigger_event(EventType event) {
for (int i = 0; i < handler_count; i++) {
if (handlers[i].callback) {
handlers[i].callback(event, handlers[i].user_data);
}
}
}
// Example callback
void log_event(EventType event, void *data) {
const char *name = (const char*)data;
const char *event_names[] = {"START", "STOP", "ERROR"};
printf("[%s] Event: %s\n", name, event_names[event]);
}
int main(void) {
register_handler(log_event, "Logger1");
register_handler(log_event, "Logger2");
trigger_event(EVENT_START);
trigger_event(EVENT_STOP);
return 0;
}
Timer Callbacks
#include <stdio.h>
#include <time.h>
typedef void (*TimerCallback)(void *data);
typedef struct {
time_t trigger_time;
TimerCallback callback;
void *data;
int active;
} Timer;
// Set timer
void set_timer(Timer *timer, int seconds, TimerCallback callback, void *data) {
timer->trigger_time = time(NULL) + seconds;
timer->callback = callback;
timer->data = data;
timer->active = 1;
}
// Check and fire timers
void check_timers(Timer *timers, int count) {
time_t now = time(NULL);
for (int i = 0; i < count; i++) {
if (timers[i].active && now >= timers[i].trigger_time) {
timers[i].callback(timers[i].data);
timers[i].active = 0;
}
}
}
void on_timer(void *data) {
printf("Timer fired: %s\n", (char*)data);
}
int main(void) {
Timer timers[3];
set_timer(&timers[0], 1, on_timer, "Timer 1");
set_timer(&timers[1], 2, on_timer, "Timer 2");
set_timer(&timers[2], 3, on_timer, "Timer 3");
// Simple event loop
while (timers[0].active || timers[1].active || timers[2].active) {
check_timers(timers, 3);
}
return 0;
}
Arrays of Function Pointers
Basic Array
#include <stdio.h>
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; }
int main(void) {
// Array of function pointers
int (*operations[4])(int, int) = {add, subtract, multiply, divide};
char *names[] = {"add", "subtract", "multiply", "divide"};
int a = 20, b = 5;
for (int i = 0; i < 4; i++) {
printf("%d %s %d = %d\n", a, names[i], b, operations[i](a, b));
}
return 0;
}
Menu-Driven Program
#include <stdio.h>
void option_new(void) { printf("Creating new file...\n"); }
void option_open(void) { printf("Opening file...\n"); }
void option_save(void) { printf("Saving file...\n"); }
void option_exit(void) { printf("Goodbye!\n"); }
typedef void (*MenuOption)(void);
typedef struct {
const char *name;
MenuOption action;
} MenuItem;
int main(void) {
MenuItem menu[] = {
{"New", option_new},
{"Open", option_open},
{"Save", option_save},
{"Exit", option_exit}
};
int menu_size = sizeof(menu) / sizeof(menu[0]);
int choice;
do {
printf("\n=== Menu ===\n");
for (int i = 0; i < menu_size; i++) {
printf("%d. %s\n", i + 1, menu[i].name);
}
printf("Choice: ");
scanf("%d", &choice);
if (choice >= 1 && choice <= menu_size) {
menu[choice - 1].action();
}
} while (choice != menu_size);
return 0;
}
Jump Table (Dispatch Table)
#include <stdio.h>
// Calculator operations
typedef enum {
OP_ADD = '+',
OP_SUB = '-',
OP_MUL = '*',
OP_DIV = '/'
} Operation;
double add(double a, double b) { return a + b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double div_op(double a, double b) { return b != 0 ? a / b : 0; }
typedef double (*BinaryOp)(double, double);
// Create dispatch table
BinaryOp get_operation(char op) {
static BinaryOp table[256] = {NULL};
static int initialized = 0;
if (!initialized) {
table['+'] = add;
table['-'] = sub;
table['*'] = mul;
table['/'] = div_op;
initialized = 1;
}
return table[(unsigned char)op];
}
double calculate(double a, char op, double b) {
BinaryOp operation = get_operation(op);
if (operation) {
return operation(a, b);
}
printf("Unknown operation: %c\n", op);
return 0;
}
int main(void) {
printf("10 + 5 = %.2f\n", calculate(10, '+', 5));
printf("10 - 5 = %.2f\n", calculate(10, '-', 5));
printf("10 * 5 = %.2f\n", calculate(10, '*', 5));
printf("10 / 5 = %.2f\n", calculate(10, '/', 5));
return 0;
}
Function Pointers in Structures
Basic Structure with Function Pointers
#include <stdio.h>
typedef struct {
int x, y;
void (*print)(struct Point *self);
void (*move)(struct Point *self, int dx, int dy);
} Point;
// Forward declaration needed
typedef struct Point Point;
void point_print(Point *self) {
printf("Point(%d, %d)\n", self->x, self->y);
}
void point_move(Point *self, int dx, int dy) {
self->x += dx;
self->y += dy;
}
Point point_create(int x, int y) {
Point p = {
.x = x,
.y = y,
.print = point_print,
.move = point_move
};
return p;
}
int main(void) {
Point p = point_create(10, 20);
p.print(&p); // Point(10, 20)
p.move(&p, 5, 5);
p.print(&p); // Point(15, 25)
return 0;
}
Object-Oriented Interface
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Shape "class" with virtual methods
typedef struct Shape {
const char *name;
double (*area)(struct Shape *self);
double (*perimeter)(struct Shape *self);
void (*draw)(struct Shape *self);
void (*destroy)(struct Shape *self);
} Shape;
// Rectangle "subclass"
typedef struct {
Shape base;
double width, height;
} Rectangle;
double rectangle_area(Shape *self) {
Rectangle *r = (Rectangle*)self;
return r->width * r->height;
}
double rectangle_perimeter(Shape *self) {
Rectangle *r = (Rectangle*)self;
return 2 * (r->width + r->height);
}
void rectangle_draw(Shape *self) {
Rectangle *r = (Rectangle*)self;
printf("Drawing rectangle: %.1f x %.1f\n", r->width, r->height);
}
void rectangle_destroy(Shape *self) {
free(self);
}
Shape* rectangle_create(double width, double height) {
Rectangle *r = malloc(sizeof(Rectangle));
r->base.name = "Rectangle";
r->base.area = rectangle_area;
r->base.perimeter = rectangle_perimeter;
r->base.draw = rectangle_draw;
r->base.destroy = rectangle_destroy;
r->width = width;
r->height = height;
return (Shape*)r;
}
// Circle "subclass"
typedef struct {
Shape base;
double radius;
} Circle;
double circle_area(Shape *self) {
Circle *c = (Circle*)self;
return 3.14159 * c->radius * c->radius;
}
double circle_perimeter(Shape *self) {
Circle *c = (Circle*)self;
return 2 * 3.14159 * c->radius;
}
void circle_draw(Shape *self) {
Circle *c = (Circle*)self;
printf("Drawing circle: radius %.1f\n", c->radius);
}
Shape* circle_create(double radius) {
Circle *c = malloc(sizeof(Circle));
c->base.name = "Circle";
c->base.area = circle_area;
c->base.perimeter = circle_perimeter;
c->base.draw = circle_draw;
c->base.destroy = rectangle_destroy; // Same as rectangle
c->radius = radius;
return (Shape*)c;
}
// Polymorphic function - works with any shape!
void print_shape_info(Shape *shape) {
printf("%s:\n", shape->name);
printf(" Area: %.2f\n", shape->area(shape));
printf(" Perimeter: %.2f\n", shape->perimeter(shape));
shape->draw(shape);
}
int main(void) {
Shape *shapes[] = {
rectangle_create(5, 3),
circle_create(4),
rectangle_create(10, 2)
};
int count = 3;
for (int i = 0; i < count; i++) {
print_shape_info(shapes[i]);
printf("\n");
}
// Cleanup
for (int i = 0; i < count; i++) {
shapes[i]->destroy(shapes[i]);
}
return 0;
}
Typedef with Function Pointers
Simplifying Function Pointer Types
// Without typedef (hard to read)
void (*callback)(int, void*);
void register_callback(void (*callback)(int, void*), void *data);
// With typedef (much cleaner)
typedef void (*Callback)(int, void*);
Callback callback;
void register_callback(Callback callback, void *data);
Complete Example
#include <stdio.h>
// Define function pointer types
typedef int (*BinaryIntOp)(int, int);
typedef void (*VoidCallback)(void);
typedef int (*Comparator)(const void*, const void*);
typedef void (*EventHandler)(int event_id, void *data);
// Operations
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; }
// Use the typedef
BinaryIntOp operations[] = {add, subtract, multiply};
int apply_operation(BinaryIntOp op, int a, int b) {
return op ? op(a, b) : 0;
}
int main(void) {
for (int i = 0; i < 3; i++) {
printf("Result: %d\n", apply_operation(operations[i], 10, 5));
}
return 0;
}
Function Pointer Returning Function Pointer
#include <stdio.h>
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 ? a / b : 0; }
// Function that returns a function pointer
// Without typedef: int (*get_operation(char op))(int, int)
// With typedef:
Operation get_operation(char op) {
switch (op) {
case '+': return add;
case '-': return subtract;
case '*': return multiply;
case '/': return divide;
default: return NULL;
}
}
int main(void) {
char operators[] = {'+', '-', '*', '/'};
for (int i = 0; i < 4; i++) {
Operation op = get_operation(operators[i]);
if (op) {
printf("20 %c 5 = %d\n", operators[i], op(20, 5));
}
}
return 0;
}
Generic Programming
Generic Sorting
#include <stdio.h>
#include <string.h>
typedef int (*CompareFunc)(const void*, const void*);
// Generic bubble sort
void generic_sort(void *base, size_t count, size_t size, CompareFunc compare) {
char *arr = (char*)base;
char *temp = malloc(size);
for (size_t i = 0; i < count - 1; i++) {
for (size_t j = 0; j < count - i - 1; j++) {
void *a = arr + j * size;
void *b = arr + (j + 1) * size;
if (compare(a, b) > 0) {
// Swap
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
}
}
}
free(temp);
}
// Compare functions
int compare_int(const void *a, const void *b) {
return *(int*)a - *(int*)b;
}
int compare_int_desc(const void *a, const void *b) {
return *(int*)b - *(int*)a;
}
int compare_string(const void *a, const void *b) {
return strcmp(*(char**)a, *(char**)b);
}
int main(void) {
// Sort integers ascending
int numbers[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(numbers) / sizeof(numbers[0]);
generic_sort(numbers, n, sizeof(int), compare_int);
printf("Sorted ascending: ");
for (int i = 0; i < n; i++) printf("%d ", numbers[i]);
printf("\n");
// Sort integers descending
generic_sort(numbers, n, sizeof(int), compare_int_desc);
printf("Sorted descending: ");
for (int i = 0; i < n; i++) printf("%d ", numbers[i]);
printf("\n");
// Sort strings
char *names[] = {"Charlie", "Alice", "Eve", "Bob", "David"};
int name_count = sizeof(names) / sizeof(names[0]);
generic_sort(names, name_count, sizeof(char*), compare_string);
printf("Sorted names: ");
for (int i = 0; i < name_count; i++) printf("%s ", names[i]);
printf("\n");
return 0;
}
Generic Map/Filter/Reduce
#include <stdio.h>
#include <stdlib.h>
typedef void (*MapFunc)(void *element);
typedef int (*FilterFunc)(const void *element);
typedef void (*ReduceFunc)(void *accumulator, const void *element);
// Generic map: apply function to each element
void generic_map(void *array, size_t count, size_t size, MapFunc func) {
char *ptr = (char*)array;
for (size_t i = 0; i < count; i++) {
func(ptr + i * size);
}
}
// Generic filter: create new array with matching elements
void* generic_filter(void *array, size_t count, size_t size,
FilterFunc predicate, size_t *result_count) {
// First pass: count matching elements
char *ptr = (char*)array;
size_t match_count = 0;
for (size_t i = 0; i < count; i++) {
if (predicate(ptr + i * size)) {
match_count++;
}
}
// Allocate result array
void *result = malloc(match_count * size);
char *dest = (char*)result;
// Second pass: copy matching elements
for (size_t i = 0; i < count; i++) {
void *element = ptr + i * size;
if (predicate(element)) {
memcpy(dest, element, size);
dest += size;
}
}
*result_count = match_count;
return result;
}
// Generic reduce: fold array into single value
void generic_reduce(void *array, size_t count, size_t size,
void *accumulator, ReduceFunc func) {
char *ptr = (char*)array;
for (size_t i = 0; i < count; i++) {
func(accumulator, ptr + i * size);
}
}
// Example functions
void double_int(void *element) {
*(int*)element *= 2;
}
int is_even(const void *element) {
return (*(int*)element % 2) == 0;
}
void sum_int(void *acc, const void *element) {
*(int*)acc += *(int*)element;
}
int main(void) {
int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
size_t count = sizeof(numbers) / sizeof(numbers[0]);
// Map: double each element
printf("Original: ");
for (size_t i = 0; i < count; i++) printf("%d ", numbers[i]);
printf("\n");
generic_map(numbers, count, sizeof(int), double_int);
printf("Doubled: ");
for (size_t i = 0; i < count; i++) printf("%d ", numbers[i]);
printf("\n");
// Filter: keep even numbers
size_t filtered_count;
int *evens = generic_filter(numbers, count, sizeof(int), is_even, &filtered_count);
printf("Even only: ");
for (size_t i = 0; i < filtered_count; i++) printf("%d ", evens[i]);
printf("\n");
// Reduce: sum all
int sum = 0;
generic_reduce(numbers, count, sizeof(int), &sum, sum_int);
printf("Sum: %d\n", sum);
free(evens);
return 0;
}
State Machines
Basic State Machine
#include <stdio.h>
// States
typedef enum {
STATE_IDLE,
STATE_RUNNING,
STATE_PAUSED,
STATE_STOPPED,
STATE_COUNT
} State;
// Events
typedef enum {
EVENT_START,
EVENT_PAUSE,
EVENT_RESUME,
EVENT_STOP,
EVENT_COUNT
} Event;
// State handler type
typedef State (*StateHandler)(Event event);
// State handlers
State state_idle(Event event) {
printf("[IDLE] ");
switch (event) {
case EVENT_START:
printf("Starting...\n");
return STATE_RUNNING;
default:
printf("Ignoring event in IDLE\n");
return STATE_IDLE;
}
}
State state_running(Event event) {
printf("[RUNNING] ");
switch (event) {
case EVENT_PAUSE:
printf("Pausing...\n");
return STATE_PAUSED;
case EVENT_STOP:
printf("Stopping...\n");
return STATE_STOPPED;
default:
printf("Continuing to run...\n");
return STATE_RUNNING;
}
}
State state_paused(Event event) {
printf("[PAUSED] ");
switch (event) {
case EVENT_RESUME:
printf("Resuming...\n");
return STATE_RUNNING;
case EVENT_STOP:
printf("Stopping...\n");
return STATE_STOPPED;
default:
printf("Still paused...\n");
return STATE_PAUSED;
}
}
State state_stopped(Event event) {
printf("[STOPPED] ");
switch (event) {
case EVENT_START:
printf("Restarting...\n");
return STATE_RUNNING;
default:
printf("Staying stopped...\n");
return STATE_STOPPED;
}
}
// State machine
typedef struct {
State current_state;
StateHandler handlers[STATE_COUNT];
} StateMachine;
void sm_init(StateMachine *sm) {
sm->current_state = STATE_IDLE;
sm->handlers[STATE_IDLE] = state_idle;
sm->handlers[STATE_RUNNING] = state_running;
sm->handlers[STATE_PAUSED] = state_paused;
sm->handlers[STATE_STOPPED] = state_stopped;
}
void sm_process_event(StateMachine *sm, Event event) {
StateHandler handler = sm->handlers[sm->current_state];
if (handler) {
sm->current_state = handler(event);
}
}
int main(void) {
StateMachine sm;
sm_init(&sm);
sm_process_event(&sm, EVENT_START); // IDLE -> RUNNING
sm_process_event(&sm, EVENT_PAUSE); // RUNNING -> PAUSED
sm_process_event(&sm, EVENT_START); // PAUSED: ignore
sm_process_event(&sm, EVENT_RESUME); // PAUSED -> RUNNING
sm_process_event(&sm, EVENT_STOP); // RUNNING -> STOPPED
sm_process_event(&sm, EVENT_START); // STOPPED -> RUNNING
return 0;
}
Table-Driven State Machine
#include <stdio.h>
typedef enum { STATE_A, STATE_B, STATE_C, STATE_COUNT } State;
typedef enum { EVENT_X, EVENT_Y, EVENT_Z, EVENT_COUNT } Event;
typedef void (*ActionFunc)(void);
// Action functions
void action_a_x(void) { printf("Action: A->B on X\n"); }
void action_a_y(void) { printf("Action: A stays on Y\n"); }
void action_b_x(void) { printf("Action: B->C on X\n"); }
void action_b_z(void) { printf("Action: B->A on Z\n"); }
void action_c_y(void) { printf("Action: C->A on Y\n"); }
void action_none(void) { }
// Transition table entry
typedef struct {
State next_state;
ActionFunc action;
} Transition;
// Transition table
static Transition transition_table[STATE_COUNT][EVENT_COUNT] = {
// STATE_A
{
{STATE_B, action_a_x}, // EVENT_X
{STATE_A, action_a_y}, // EVENT_Y
{STATE_A, action_none} // EVENT_Z
},
// STATE_B
{
{STATE_C, action_b_x}, // EVENT_X
{STATE_B, action_none}, // EVENT_Y
{STATE_A, action_b_z} // EVENT_Z
},
// STATE_C
{
{STATE_C, action_none}, // EVENT_X
{STATE_A, action_c_y}, // EVENT_Y
{STATE_C, action_none} // EVENT_Z
}
};
State process(State current, Event event) {
Transition t = transition_table[current][event];
t.action();
return t.next_state;
}
int main(void) {
State state = STATE_A;
const char *state_names[] = {"A", "B", "C"};
Event events[] = {EVENT_X, EVENT_Y, EVENT_X, EVENT_Z, EVENT_Y};
const char *event_names[] = {"X", "Y", "X", "Z", "Y"};
printf("Starting in state %s\n", state_names[state]);
for (int i = 0; i < 5; i++) {
printf("Event %s: ", event_names[i]);
state = process(state, events[i]);
printf("New state: %s\n", state_names[state]);
}
return 0;
}
Plugin Architecture
Simple Plugin System
#include <stdio.h>
#include <string.h>
#define MAX_PLUGINS 10
// Plugin interface
typedef struct {
const char *name;
const char *version;
int (*init)(void);
void (*shutdown)(void);
int (*process)(const char *input, char *output, size_t output_size);
} Plugin;
// Plugin registry
static Plugin plugins[MAX_PLUGINS];
static int plugin_count = 0;
// Register plugin
int register_plugin(Plugin *plugin) {
if (plugin_count >= MAX_PLUGINS) {
return -1;
}
plugins[plugin_count++] = *plugin;
printf("Registered plugin: %s v%s\n", plugin->name, plugin->version);
return 0;
}
// Initialize all plugins
void init_all_plugins(void) {
for (int i = 0; i < plugin_count; i++) {
if (plugins[i].init) {
plugins[i].init();
}
}
}
// Shutdown all plugins
void shutdown_all_plugins(void) {
for (int i = plugin_count - 1; i >= 0; i--) {
if (plugins[i].shutdown) {
plugins[i].shutdown();
}
}
}
// Find and use plugin
int use_plugin(const char *name, const char *input, char *output, size_t size) {
for (int i = 0; i < plugin_count; i++) {
if (strcmp(plugins[i].name, name) == 0) {
if (plugins[i].process) {
return plugins[i].process(input, output, size);
}
}
}
return -1;
}
// Example plugins
int uppercase_init(void) {
printf("Uppercase plugin initialized\n");
return 0;
}
void uppercase_shutdown(void) {
printf("Uppercase plugin shut down\n");
}
int uppercase_process(const char *input, char *output, size_t size) {
size_t len = strlen(input);
if (len >= size) len = size - 1;
for (size_t i = 0; i < len; i++) {
output[i] = toupper(input[i]);
}
output[len] = '\0';
return 0;
}
int reverse_process(const char *input, char *output, size_t size) {
size_t len = strlen(input);
if (len >= size) len = size - 1;
for (size_t i = 0; i < len; i++) {
output[i] = input[len - 1 - i];
}
output[len] = '\0';
return 0;
}
int main(void) {
// Create and register plugins
Plugin uppercase = {
.name = "uppercase",
.version = "1.0",
.init = uppercase_init,
.shutdown = uppercase_shutdown,
.process = uppercase_process
};
Plugin reverse = {
.name = "reverse",
.version = "1.0",
.init = NULL,
.shutdown = NULL,
.process = reverse_process
};
register_plugin(&uppercase);
register_plugin(&reverse);
init_all_plugins();
// Use plugins
char output[100];
use_plugin("uppercase", "hello world", output, sizeof(output));
printf("Uppercase: %s\n", output);
use_plugin("reverse", "hello world", output, sizeof(output));
printf("Reverse: %s\n", output);
shutdown_all_plugins();
return 0;
}
Common Patterns
Strategy Pattern
#include <stdio.h>
// Strategy interface
typedef void (*CompressionStrategy)(const char *data);
void compress_zip(const char *data) {
printf("Compressing '%s' using ZIP\n", data);
}
void compress_gzip(const char *data) {
printf("Compressing '%s' using GZIP\n", data);
}
void compress_lz4(const char *data) {
printf("Compressing '%s' using LZ4\n", data);
}
// Context
typedef struct {
CompressionStrategy strategy;
} Compressor;
void compressor_set_strategy(Compressor *c, CompressionStrategy strategy) {
c->strategy = strategy;
}
void compressor_compress(Compressor *c, const char *data) {
if (c->strategy) {
c->strategy(data);
}
}
int main(void) {
Compressor compressor = {0};
const char *data = "sample data";
compressor_set_strategy(&compressor, compress_zip);
compressor_compress(&compressor, data);
compressor_set_strategy(&compressor, compress_gzip);
compressor_compress(&compressor, data);
compressor_set_strategy(&compressor, compress_lz4);
compressor_compress(&compressor, data);
return 0;
}
Observer Pattern
#include <stdio.h>
#define MAX_OBSERVERS 10
typedef void (*Observer)(int value);
typedef struct {
int value;
Observer observers[MAX_OBSERVERS];
int observer_count;
} Subject;
void subject_init(Subject *s) {
s->value = 0;
s->observer_count = 0;
}
void subject_attach(Subject *s, Observer observer) {
if (s->observer_count < MAX_OBSERVERS) {
s->observers[s->observer_count++] = observer;
}
}
void subject_notify(Subject *s) {
for (int i = 0; i < s->observer_count; i++) {
s->observers[i](s->value);
}
}
void subject_set_value(Subject *s, int value) {
s->value = value;
subject_notify(s);
}
// Observers
void logger(int value) {
printf("Logger: value changed to %d\n", value);
}
void alert(int value) {
if (value > 100) {
printf("ALERT: Value %d exceeds threshold!\n", value);
}
}
void display(int value) {
printf("Display: [%d]\n", value);
}
int main(void) {
Subject subject;
subject_init(&subject);
subject_attach(&subject, logger);
subject_attach(&subject, alert);
subject_attach(&subject, display);
subject_set_value(&subject, 50);
printf("\n");
subject_set_value(&subject, 150);
return 0;
}
Pitfalls and Best Practices
Common Pitfalls
1. Calling NULL Function Pointer
// BAD: Crashes if fp is NULL
int (*fp)(int) = NULL;
int result = fp(42); // Crash!
// GOOD: Always check
if (fp != NULL) {
int result = fp(42);
}
2. Incorrect Function Signature
// BAD: Signature mismatch
int add(int a, int b) { return a + b; }
void (*fp)(int) = (void (*)(int))add; // Wrong!
// GOOD: Match signatures exactly
int (*fp)(int, int) = add; // Correct
3. Dangling Function Pointers
// In a dynamically loaded library scenario:
void *handle = dlopen("plugin.so", RTLD_NOW);
void (*func)(void) = dlsym(handle, "plugin_func");
// BAD: Using func after closing
dlclose(handle);
func(); // Undefined behavior!
// GOOD: Close only when done
// ... use func ...
dlclose(handle);
4. Forgetting const for Comparison Functions
// BAD: May cause undefined behavior with qsort
int compare(void *a, void *b) { ... }
// GOOD: Use const for qsort-style comparators
int compare(const void *a, const void *b) { ... }
Best Practices
1. Use Typedef for Clarity
// Hard to read
void register(void (*callback)(int, void*), void *data);
// Much cleaner
typedef void (*Callback)(int, void*);
void register(Callback callback, void *data);
2. Document Function Pointer Contracts
/**
* Comparison function for sorting.
*
* @param a Pointer to first element
* @param b Pointer to second element
* @return negative if a < b, zero if a == b, positive if a > b
*/
typedef int (*CompareFunc)(const void *a, const void *b);
3. Use Default/No-op Functions
void default_callback(int event) {
// No-op - intentionally empty
}
typedef struct {
void (*on_event)(int);
} Handler;
// Initialize with default instead of NULL
Handler h = { .on_event = default_callback };
// Now safe to call without checking
h.on_event(42);
4. Use Const When Appropriate
// Function won't be changed
void process(const void (*handler)(int event));
// Array of function pointers that won't change
const Operation operations[] = { add, subtract, multiply };
Summary
Key Concepts
- •Declaration:
return_type (*name)(params) - •Assignment:
fp = function_name(& is optional) - •Calling:
fp(args)or(*fp)(args) - •Typedef: Simplifies complex declarations
- •Arrays: Implement jump tables and dispatch tables
- •Callbacks: Pass behavior as parameters
- •Structures: Implement OOP-like patterns
Common Use Cases
| Pattern | Description |
|---|---|
| Callbacks | Notify caller of events or completion |
| Comparators | Custom sorting/searching behavior |
| State Machines | Clean state transition logic |
| Plugin Systems | Extensible application architecture |
| Strategy Pattern | Runtime algorithm selection |
| Virtual Methods | OOP polymorphism in C |
Quick Reference
// Declaration
int (*fp)(int, int);
// With typedef
typedef int (*BinaryOp)(int, int);
BinaryOp fp;
// Array of function pointers
int (*ops[4])(int, int) = {add, sub, mul, div};
// Function returning function pointer
typedef int (*Op)(int, int);
Op get_operation(char symbol);
// In structure
typedef struct {
void (*init)(void);
void (*process)(int);
void (*cleanup)(void);
} Interface;
Best Practices Summary
- •Always check for NULL before calling
- •Use typedef for readability
- •Match function signatures exactly
- •Document callback contracts
- •Consider using default/no-op functions
- •Use const where appropriate
- •Be careful with dynamic library functions
Function pointers are a powerful feature that enables flexible, extensible, and elegant C programs. Mastering them opens the door to implementing many advanced programming patterns.