When implementing menu-driven console applications in C, developers traditionally route user input through lengthy switch-case blocks. While straightforward initially, this pattern introduces deep nesting, repetitive parsing code, and fragile expansion paths. Every new command demands additional case labels, increasing cognitive load and merge conflicts.
The Function Pointer Array Pattern
Array-based dispatch replaces conditional branching by storing executable addresses in a contiguous memory layout. When the user provides a numeric selection, the application uses that number as an array subscript. The retrieved pointer is immediately invoked, executing the targeted routine without evaluating multiple branches.
Pointer Syntax Deconstruction
Declaring a callable array requires strict adherence to C's binding rules. Examine the following definition:
double (*route_ops[5])(double, double) = {NULL, func_add, func_sub, func_mul, func_div};
route_opsbinds to the[5]bracket first, establishing it as a fixed-size container.- Each slot holds a pointer to a function that accepts two
doublearguments and yields adoubleoutcome. - Position
0contains a null reference. This artificial gap aligns internal indices with human-friendly 1-based menus, enabling direct subscripting of raw keystrokes.
Refactored Architecture
The implementation below eliminates branching logic entirely. Operations are registered statically, and the control loop focuses solely on I/O management.
#include <stdio.h>
typedef double (*processor_t)(double, double);
double execute_addition(double left, double right) { return left + right; }
double execute_reduction(double left, double right) { return left - right; }
double execute_scaling(double left, double right) { return left * right; }
double execute_partition(double left, double right) { return right != 0.0 ? left / right : 0.0; }
void display_panel(void) {
puts("--- Calculation Module ---");
puts("[1] Sum [2] Difference");
puts("[3] Product [4] Quotient");
puts("[0] Abort Process");
printf("Select operation: ");
}
int main(void) {
processor_t actions[] = {NULL, execute_addition, execute_reduction,
execute_scaling, execute_partition};
int choice = 1;
double val_x, val_y, answer;
while (choice != 0) {
display_panel();
if (scanf("%d", &choice) != 1 || choice < 0 || choice > 4) {
puts("Invalid selection. Resetting input stream.\n");
while (getchar() != '\n');
continue;
}
if (choice == 0) break;
printf("Input parameter one: "); scanf("%lf", &val_x);
printf("Input parameter two: "); scanf("%lf", &val_y);
answer = actions[choice](val_x, val_y);
printf("Computed output: %.2f\n\n", answer);
}
puts("Program terminated cleanly.");
return 0;
}
Execution Flow
Once the menu validates a selection, the expression actions[choice] fetches the function pointer. Parentheses around the pointer variable ensure proper dereferencing before argument passing. The compiler generates a single indirect jump instruction to the appropriate memory address, discarding the overhead of comparing multiple constants.
Extending this model requires zero modifications to the evaluation loop. Developers only append a new routine prototype and insert its identifier into the initializer list. As long as the parameter contract remains consistant, the dispatcher remains oblivious to internal complexity.
This decoupling strategy proves valuable across various domains: registering plugin handlers, mapping string commands to behavior functions, building finite state machines, or implementing strategy patterns where algorithms swap at runtime. The compilation unit stays flat, unit tests isolate individual processors, and the routing layer never entangles with domain logic.