C Programming: Low-Level Mastery
Advanced Topics

Function Pointers

Master function pointers for callbacks, plugin systems, dispatch tables, and functional programming patterns. Learn syntax, typedef, arrays of function pointers, and building flexible architectures in C.

Understanding Function Pointers

Function pointers store addresses of functions, enabling runtime selection of behavior. Pass functions as arguments, return functions from functions, store functions in data structures. Essential for callbacks, event handling, plugins, and dynamic dispatch. Syntax is complex but powerful.

C
#include <stdio.h>

/* Basic function */
int add(int a, int b) {
    return a + b;
}

/* Function pointer declaration */
int (*func_ptr)(int, int);  /* Pointer to function returning int, taking two ints */

/* Usage */
void function_pointer_example(void) {
    /* Assign function address */
    func_ptr = add;  /* Or: func_ptr = &add; */
    
    /* Call through pointer */
    int result = func_ptr(10, 20);  /* Or: (*func_ptr)(10, 20); */
    printf("Result: %d\n", result);  /* 30 */
}

/* Syntax breakdown */
/*
   int (*ptr)(int, int);
   ^   ^  ^   ^^^^^^^^^
   |   |  |   Parameters
   |   |  Function name
   |   Pointer declaration
   Return type
   
   Parentheses are crucial!
   int *ptr(int, int);  - Function returning int*
   int (*ptr)(int, int); - Pointer to function returning int
*/

/* Example: Calculator */
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 a / b; }

void calculator_example(void) {
    int (*operation)(int, int);
    char op;
    int a = 20, b = 10;
    
    printf("Enter operation (+, -, *, /): ");
    scanf(" %c", &op);
    
    switch (op) {
        case '+': operation = add; break;
        case '-': operation = subtract; break;
        case '*': operation = multiply; break;
        case '/': operation = divide; break;
        default: return;
    }
    
    printf("Result: %d\n", operation(a, b));
}

Typedef for Function Pointers

Function pointer syntax is notoriously complex. typedef creates aliases making code readable. Define function pointer types once, use them throughout code. Standard pattern for clean, maintainable function pointer usage.

C
/* Without typedef (ugly) */
int (*operation)(int, int);
void process(int (*func)(int, int));

/* With typedef (clean) */
typedef int (*BinaryOp)(int, int);

BinaryOp operation;
void process(BinaryOp func);

/* More examples */
typedef void (*Callback)(void);
typedef int (*Comparator)(const void*, const void*);
typedef char* (*StringTransform)(const char*);

/* Using typedefs */
void execute_callback(Callback cb) {
    if (cb != NULL) {
        cb();
    }
}

/* Array of function pointers */
typedef int (*MathOp)(int, int);

MathOp operations[] = {
    add,
    subtract,
    multiply,
    divide
};

int result = operations[0](10, 20);  /* add(10, 20) */

/* Structure containing function pointers */
typedef void (*InitFunc)(void);
typedef void (*UpdateFunc)(float dt);
typedef void (*DrawFunc)(void);

typedef struct {
    InitFunc init;
    UpdateFunc update;
    DrawFunc draw;
} GameObject;

GameObject player = {
    .init = player_init,
    .update = player_update,
    .draw = player_draw
};

/* Function pointer as struct member */
typedef struct {
    const char *name;
    int (*execute)(int, int);
} Command;

Command commands[] = {
    {"add", add},
    {"sub", subtract},
    {"mul", multiply},
    {"div", divide}
};

/* Lookup and execute */
int execute_command(const char *name, int a, int b) {
    for (size_t i = 0; i < 4; i++) {
        if (strcmp(commands[i].name, name) == 0) {
            return commands[i].execute(a, b);
        }
    }
    return 0;
}

/* Callbacks */
typedef void (*ProgressCallback)(int percent);

void long_operation(ProgressCallback callback) {
    for (int i = 0; i <= 100; i += 10) {
        /* Do work... */
        
        if (callback != NULL) {
            callback(i);  /* Report progress */
        }
    }
}

void progress_handler(int percent) {
    printf("Progress: %d%%\n", percent);
}

/* Usage */
long_operation(progress_handler);

/* Multiple callback types */
typedef void (*ErrorCallback)(const char *msg);
typedef void (*SuccessCallback)(void);

typedef struct {
    ErrorCallback on_error;
    SuccessCallback on_success;
} Callbacks;

void perform_operation(Callbacks *cbs) {
    if (/* error */) {
        if (cbs-&gt;on_error != NULL) {
            cbs-&gt;on_error("Operation failed");
        }
    } else {
        if (cbs-&gt;on_success != NULL) {
            cbs-&gt;on_success();
        }
    }
}

Common Patterns and Use Cases

Function pointers enable powerful patterns: qsort-style callbacks, plugin architectures, state machines, event systems, command patterns. Understanding these patterns helps design flexible systems.

C
/* Pattern 1: qsort callback */
int compare_ints(const void *a, const void *b) {
    int ia = *(const int*)a;
    int ib = *(const int*)b;
    return (ia &gt; ib) - (ia < ib);
}

int arr[] = {5, 2, 8, 1, 9};
qsort(arr, 5, sizeof(int), compare_ints);

/* Pattern 2: Dispatch table */
typedef enum {
    OP_ADD,
    OP_SUB,
    OP_MUL,
    OP_DIV
} OpCode;

typedef int (*OpHandler)(int, int);

OpHandler handlers[] = {
    [OP_ADD] = add,
    [OP_SUB] = subtract,
    [OP_MUL] = multiply,
    [OP_DIV] = divide
};

int execute(OpCode op, int a, int b) {
    if (op < 0 || op >= 4) {
        return 0;
    }
    return handlers[op](a, b);
}

/* Pattern 3: State machine */
typedef enum {
    STATE_IDLE,
    STATE_RUNNING,
    STATE_PAUSED,
    STATE_STOPPED
} State;

typedef State (*StateHandler)(void);

State handle_idle(void) {
    printf("Idle state\n");
    return STATE_RUNNING;
}

State handle_running(void) {
    printf("Running state\n");
    return STATE_PAUSED;
}

State handle_paused(void) {
    printf("Paused state\n");
    return STATE_RUNNING;
}

State handle_stopped(void) {
    printf("Stopped state\n");
    return STATE_IDLE;
}

StateHandler state_handlers[] = {
    [STATE_IDLE] = handle_idle,
    [STATE_RUNNING] = handle_running,
    [STATE_PAUSED] = handle_paused,
    [STATE_STOPPED] = handle_stopped
};

void run_state_machine(void) {
    State current = STATE_IDLE;
    
    for (int i = 0; i < 10; i++) {
        current = state_handlers[current]();
    }
}

/* Pattern 4: Plugin system */
typedef struct {
    const char *name;
    void (*init)(void);
    void (*update)(float);
    void (*shutdown)(void);
} Plugin;

void register_plugin(Plugin *plugin) {
    if (plugin-&gt;init != NULL) {
        plugin-&gt;init();
    }
}

/* Pattern 5: Event system */
typedef enum {
    EVENT_MOUSE_CLICK,
    EVENT_KEY_PRESS,
    EVENT_WINDOW_RESIZE
} EventType;

typedef void (*EventHandler)(void *data);

typedef struct {
    EventType type;
    EventHandler handler;
} EventListener;

EventListener listeners[10];
int listener_count = 0;

void register_listener(EventType type, EventHandler handler) {
    listeners[listener_count].type = type;
    listeners[listener_count].handler = handler;
    listener_count++;
}

void dispatch_event(EventType type, void *data) {
    for (int i = 0; i < listener_count; i++) {
        if (listeners[i].type == type) {
            listeners[i].handler(data);
        }
    }
}

/* Pattern 6: Iterator */
typedef int (*FilterFunc)(int);

void filter_array(int *arr, int size, FilterFunc filter) {
    for (int i = 0; i < size; i++) {
        if (filter(arr[i])) {
            printf("%d ", arr[i]);
        }
    }
}

int is_even(int x) { return x % 2 == 0; }
int is_positive(int x) { return x &gt; 0; }

int arr[] = {-2, -1, 0, 1, 2, 3, 4};
filter_array(arr, 7, is_even);     /* -2 0 2 4 */
filter_array(arr, 7, is_positive);  /* 1 2 3 4 */

/* Pattern 7: Strategy pattern */
typedef int (*SortStrategy)(int*, int);

int bubble_sort(int *arr, int size) { /* ... */ }
int quick_sort(int *arr, int size) { /* ... */ }
int merge_sort(int *arr, int size) { /* ... */ }

void sort_data(int *arr, int size, SortStrategy strategy) {
    strategy(arr, size);
}

/* Pattern 8: Functional programming */
typedef int (*MapFunc)(int);
typedef int (*ReduceFunc)(int, int);

void map(int *arr, int size, MapFunc func) {
    for (int i = 0; i < size; i++) {
        arr[i] = func(arr[i]);
    }
}

int reduce(int *arr, int size, ReduceFunc func, int initial) {
    int result = initial;
    for (int i = 0; i < size; i++) {
        result = func(result, arr[i]);
    }
    return result;
}

int double_value(int x) { return x * 2; }
int sum(int a, int b) { return a + b; }

int arr[] = {1, 2, 3, 4, 5};
map(arr, 5, double_value);  /* {2, 4, 6, 8, 10} */
int total = reduce(arr, 5, sum, 0);  /* 30 */

Advanced Function Pointer Techniques

Advanced techniques: returning function pointers, closures (limited), vtables for polymorphism, signal handlers. These patterns enable sophisticated designs approaching object-oriented capabilities in C.

C
/* Returning function pointers */
typedef int (*MathOp)(int, int);

MathOp get_operation(char op) {
    switch (op) {
        case '+': return add;
        case '-': return subtract;
        case '*': return multiply;
        case '/': return divide;
        default: return NULL;
    }
}

MathOp operation = get_operation('+');
if (operation != NULL) {
    int result = operation(10, 20);
}

/* Vtable pattern (OOP in C) */
typedef struct {
    void (*draw)(void *self);
    void (*update)(void *self);
    const char* (*get_name)(void *self);
} ShapeVTable;

typedef struct {
    ShapeVTable *vtable;
    int x, y;
} Shape;

typedef struct {
    Shape base;
    int radius;
} Circle;

void circle_draw(void *self) {
    Circle *c = (Circle*)self;
    printf("Drawing circle at (%d, %d) radius %d\n",
           c-&gt;base.x, c-&gt;base.y, c-&gt;radius);
}

void circle_update(void *self) {
    /* Update logic */
}

const char* circle_get_name(void *self) {
    return "Circle";
}

ShapeVTable circle_vtable = {
    .draw = circle_draw,
    .update = circle_update,
    .get_name = circle_get_name
};

Circle* create_circle(int x, int y, int radius) {
    Circle *c = malloc(sizeof(Circle));
    c-&gt;base.vtable = &circle_vtable;
    c-&gt;base.x = x;
    c-&gt;base.y = y;
    c-&gt;radius = radius;
    return c;
}

/* Polymorphic call */
void draw_shape(Shape *shape) {
    shape-&gt;vtable-&gt;draw(shape);
}

/* Closure simulation (limited) */
typedef struct {
    int (*func)(int);
    int context;
} Closure;

int add_n(int x, Closure *closure) {
    return x + closure-&gt;context;
}

Closure make_adder(int n) {
    Closure c = {
        .func = (int (*)(int))add_n,
        .context = n
    };
    return c;
}

/* Signal handlers */
#include <signal.h>

void signal_handler(int sig) {
    if (sig == SIGINT) {
        printf("Caught Ctrl+C\n");
    }
}

void setup_handlers(void) {
    signal(SIGINT, signal_handler);
}

/* Function pointer arrays (jump table) */
typedef void (*MenuHandler)(void);

void menu_new(void) { printf("New file\n"); }
void menu_open(void) { printf("Open file\n"); }
void menu_save(void) { printf("Save file\n"); }
void menu_quit(void) { printf("Quit\n"); }

MenuHandler menu_handlers[] = {
    menu_new,
    menu_open,
    menu_save,
    menu_quit
};

void handle_menu_selection(int choice) {
    if (choice >= 0 && choice < 4) {
        menu_handlers[choice]();
    }
}

/* Chaining callbacks */
typedef void (*TransformFunc)(int*);

void apply_transforms(int *value, TransformFunc *transforms, int count) {
    for (int i = 0; i < count; i++) {
        transforms[i](value);
    }
}

void double_it(int *x) { *x *= 2; }
void add_ten(int *x) { *x += 10; }
void square_it(int *x) { *x *= *x; }

TransformFunc chain[] = {double_it, add_ten, square_it};
int value = 5;
apply_transforms(&value, chain, 3);  /* ((5*2)+10)^2 = 400 */

/* Function pointer comparison */
void compare_pointers(void) {
    MathOp op1 = add;
    MathOp op2 = add;
    MathOp op3 = subtract;
    
    if (op1 == op2) {
        printf("Same function\n");
    }
    
    if (op1 != op3) {
        printf("Different functions\n");
    }
}

/* NULL checking */
void safe_call(void (*func)(void)) {
    if (func != NULL) {
        func();
    } else {
        printf("NULL function pointer\n");
    }
}

Best Practices and Pitfalls

Function pointers are powerful but dangerous. Always check for NULL before calling. Match signatures exactly. Use typedef for readability. Understand calling conventions. Document expected behavior. Test thoroughly.

C
/* Best practice: Always check NULL */
void safe_callback(void (*callback)(void)) {
    if (callback != NULL) {  /* Always check! */
        callback();
    }
}

/* Best practice: Use typedef */
/* BAD */
void process(int (*func)(int, int));

/* GOOD */
typedef int (*BinaryOp)(int, int);
void process(BinaryOp func);

/* Best practice: Document contracts */
/**
 * Comparator for sorting
 * @param a First element
 * @param b Second element
 * @return <0 if a<b, 0 if a==b, > 0 if a>b
 */
typedef int (*Comparator)(const void *a, const void *b);

/* Pitfall 1: Signature mismatch */
void wrong_signature(int x) { /* ... */ }
void (*func)(void) = wrong_signature;  /* WRONG! */
func();  /* Undefined behavior */

/* Pitfall 2: Forgetting to dereference */
void my_func(void) { /* ... */ }

void (*ptr)(void) = my_func;
// ptr;  /* Wrong: Just address */
ptr();   /* Right: Call function */

/* Pitfall 3: Dangling pointers */
void* get_function(void) {
    void local_func(void) { /* ... */ }  /* Nested function (GCC) */
    return local_func;  /* WRONG: Returns local address */
}

/* Pitfall 4: Casting function pointers */
void func_int(int x) { /* ... */ }
void (*generic)(void) = (void (*)(void))func_int;  /* Dangerous */
generic();  /* Undefined behavior */

/* Safe alternative: Use void* context */
typedef void (*GenericFunc)(void *context);

void safe_wrapper(void *ctx) {
    int *value = (int*)ctx;
    func_int(*value);
}

/* Pitfall 5: ABI incompatibility */
#ifdef _WIN32
    #define CALLBACK __stdcall  /* Windows calling convention */
#else
    #define CALLBACK
#endif

typedef void (CALLBACK *WindowsCallback)(int);

/* Best practice: Consistent naming */
/* Suffix convention */
typedef void (*InitFunc)(void);
typedef void (*UpdateFunc)(void);
typedef void (*CleanupFunc)(void);

/* Prefix convention */
typedef int (*CompareFunc)(const void*, const void*);
typedef void (*ProcessFunc)(void*);

/* Best practice: Version management */
typedef struct {
    int version;
    void (*init)(void);
    void (*process)(void);
    /* v2 additions */
    void (*cleanup)(void);  /* NULL in v1 */
} PluginAPI;

void call_plugin(PluginAPI *plugin) {
    if (plugin-&gt;version >= 1) {
        plugin-&gt;init();
        plugin-&gt;process();
    }
    if (plugin-&gt;version >= 2 && plugin-&gt;cleanup != NULL) {
        plugin-&gt;cleanup();
    }
}

/* Best practice: Clear ownership */
typedef void (*DataCallback)(void *data);

/* Document: Callback does NOT take ownership */
void set_callback(DataCallback cb, void *data);

/* Document: Callback takes ownership */
void set_callback_transfer(DataCallback cb, void *data);

/* Thread safety */
#include <pthread.h>

typedef struct {
    void (*func)(void*);
    pthread_mutex_t lock;
} ThreadSafeCallback;

void call_threadsafe(ThreadSafeCallback *cb, void *arg) {
    pthread_mutex_lock(&cb-&gt;lock);
    if (cb-&gt;func != NULL) {
        cb-&gt;func(arg);
    }
    pthread_mutex_unlock(&cb-&gt;lock);
}

Summary & What's Next

Key Takeaways:

  • ✅ Function pointers store function addresses
  • ✅ Use typedef for readable function pointer types
  • ✅ Always check for NULL before calling
  • ✅ Enable callbacks, plugins, dispatch tables
  • ✅ Essential for flexible architectures
  • ✅ Signatures must match exactly
  • ✅ Powerful for implementing OOP-like patterns
  • ✅ Test thoroughly - errors are runtime

What's Next?

Let's learn about inline assembly for low-level system programming!