Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Function Pointer Arrays in C
Build a dispatch table: an array of function pointers that selects behaviour by index. A clean pattern for menus, command parsers and embedded firmware.
An array of function pointers in C lets a program choose which function to run by using an index instead of a long chain of if statements. This is called a dispatch table, and it is one of the most useful patterns in embedded C programming. In this free C tutorial you will build one from scratch, add safe index checking, attach a readable name to each entry, and learn when a table beats a switch and when it does not.
What You Will Learn
- How to declare and initialise an array of function pointers in C
- The correct call syntax, including a common notation mistake
- How to pair names and handlers in a struct table
- Bounds checking with
sizeofso a bad index never crashes the program - An honest comparison of dispatch tables and switch statements
- How this pattern supports C programming for embedded systems
Prerequisites
You need the previous two lectures on calling through function pointers and callbacks, plus arrays and struct basics from earlier chapters.
Why Use an Array of Function Pointers
Suppose a device accepts four commands: add, subtract, multiply and divide. You might write a switch that tests the command number and calls the right function. That works, but every new command means editing the switch. With a table, each command number is simply a position in an array, and the program calls whatever address it finds there.
The idea becomes powerful in many applications where you do not know in advance which function will run. The decision arrives at run time as a number: a menu choice, a received command byte, an interrupt number or a state identifier.
Declaring an Array of Function Pointers in C
Start from a single pointer declaration and add square brackets after the name:
int (*ops[4])(int, int); /* array of 4 pointers to int f(int, int) */
int (*ops2[])(int, int) = { add, sub, mul, div }; /* size from initialiser */
The brackets attach to the name first, so ops is an array, its elements are pointers, and those pointers point to functions. With a typedef the whole thing is much easier to read:
typedef int (*binop_fn)(int, int);
binop_fn ops[4];
Calling an Element
Because ops[1] is itself a function pointer, you call it by subscripting and then applying the call operator:
r = ops[1](12, 5); /* preferred */
r = (*ops[1])(12, 5); /* explicit dereference, equivalent */
Warning: do not write (*ops)[1](a, b)
Some older books show the form (*ops)[1](a, b). That dereferences the array name first, which gives you only the first element, and then subscripts it, which is wrong for an array of pointers. The correct explicit form puts the star after the subscript: (*ops[1])(a, b). Prefer the plain ops[1](a, b) and avoid the confusion.
Complete Dispatch Table Example
The next program pairs each handler with a name in a struct, keeps the table const, and computes its length with sizeof so adding a row automatically updates the bounds check. Division reports an error rather than crashing when the divisor is zero. To keep the output predictable, a fixed list of test commands replaces keyboard input. Save it as ep_dispatch.c.
#include <stdio.h>
#include <stddef.h>
typedef int (*binop_fn)(int, int, int *);
static int op_add(int a, int b, int *out)
{
*out = a + b;
return 0;
}
static int op_sub(int a, int b, int *out)
{
*out = a - b;
return 0;
}
static int op_mul(int a, int b, int *out)
{
*out = a * b;
return 0;
}
static int op_div(int a, int b, int *out)
{
if (b == 0)
return -1;
*out = a / b;
return 0;
}
struct op_entry {
const char *name;
binop_fn fn;
};
static const struct op_entry op_table[] = {
{ "add", op_add },
{ "sub", op_sub },
{ "mul", op_mul },
{ "div", op_div },
};
#define OP_COUNT (sizeof op_table / sizeof op_table[0])
int main(void)
{
/* each row: { table index, a, b } */
const int script[][3] = {
{ 0, 12, 5 }, { 1, 12, 5 }, { 2, 12, 5 },
{ 3, 12, 5 }, { 3, 12, 0 }, { 9, 1, 1 },
};
for (size_t i = 0; i < sizeof script / sizeof script[0]; i++) {
int idx = script[i][0];
int a = script[i][1];
int b = script[i][2];
int result = 0;
if (idx < 0 || (size_t)idx >= OP_COUNT) {
printf("index %d : rejected (table has %zu entries)\n",
idx, OP_COUNT);
continue;
}
if (op_table[idx].fn(a, b, &result) != 0)
printf("%s(%d, %d) : error\n", op_table[idx].name, a, b);
else
printf("%s(%d, %d) = %d\n", op_table[idx].name, a, b, result);
}
return 0;
}
Build and Run
gcc -Wall -Wextra -std=c11 -o ep_dispatch ep_dispatch.c
./ep_dispatch
Expected output:
add(12, 5) = 17
sub(12, 5) = 7
mul(12, 5) = 60
div(12, 5) = 2
div(12, 0) : error
index 9 : rejected (table has 4 entries)
How the Program Works
- Each row of
scriptholds a command index and two operands. OP_COUNTdivides the table’s total size by one element’s size, giving 4.- The check
idx < 0 || (size_t)idx >= OP_COUNTrejects invalid values before they touch the table. The cast keeps the comparison between unsigned values. - Each handler returns 0 for success and -1 for failure, and writes its answer through a pointer, so errors never hide inside a normal-looking number.
- The last row uses index 9. Without the bounds check this would read memory beyond the table and jump to garbage.
Dispatch Table or Switch Statement
Older tutorials claim that function pointer tables are faster than a switch. That is not a safe rule. Modern compilers often turn a dense switch into a jump table of their own, and can inline the cases, which an indirect call prevents. The real advantages of a table are different.
| Aspect | Dispatch table | Switch statement |
|---|---|---|
| Adding a command | Add one row | Edit the switch body |
| Data-driven | Yes, table can be const data or even loaded at run time | No, logic is compiled in |
| Inlining | Not possible across the indirect call | Possible |
| Readability for few cases | Overkill | Clear |
| Readability for many cases | Compact and regular | Long |
| Speed | Constant-time lookup; measure on your target | Often equal; measure on your target |
Choose a table when the set of actions is large, regular, or likely to grow. Choose a switch for a few cases with different shapes.
Real-World Uses of an Array of Function Pointers in C
- Command parsers: a received byte or text token indexes a handler table.
- State machines: each state has an entry function, and the current state number picks it.
- Interrupt vector tables: the hardware itself indexes an array of handler addresses.
- Protocol stacks: a message type selects its decoder.
Common Mistakes with an Array of Function Pointers in C
| Mistake | Result | Fix |
|---|---|---|
| No bounds check on the index | Out-of-range read, jump to random address | Compare against the table length first |
| Hard-coding the length | Table and check drift apart | Use sizeof table / sizeof table[0] |
| Leaving an entry NULL | Crash on call | Fill every slot or test for NULL, and use a default handler |
| Handlers with different signatures | Incompatible pointer errors | Make all handlers share one typedef |
Table not const | Lives in RAM and can be overwritten | Declare it const so it stays in flash |
Best Practices for an Array of Function Pointers in C
- Give every handler the same signature and a shared typedef.
- Keep the table
static const, and put name strings next to the handlers. - Validate indexes that come from outside the program: keyboard, UART, network.
- Provide a default handler for unused slots rather than leaving NULL.
Security Considerations
A dispatch index that comes from a network message or serial port is untrusted input. A missing bounds check turns it into an arbitrary jump. Always validate it, and prefer read-only tables so an overflow elsewhere cannot rewrite a handler address.
Summary and Key Takeaways
- An array of function pointers in C maps an index to a function:
ops[i](a, b). - Use a typedef, a struct with a name, and a
consttable. - Always bounds-check the index before use.
- Tables are about flexibility and clarity, not guaranteed speed.
- Avoid the faulty
(*ops)[i](a, b)notation.
Conclusion
With an array of function pointers in C, you can now replace sprawling conditionals with a compact table that is safe, easy to extend and friendly to flash memory. In the next lecture we step back and learn to read the pointer declarations that appeared in this chapter, from simple pointers to arrays of function pointers, with a reliable method for decoding them.
Frequently Asked Questions
How do I declare an array of function pointers in C?
Add brackets to the name in a function pointer declaration, for example int (*ops[4])(int, int);, or use a typedef and write binop_fn ops[4];.
How do I call a function from an array of pointers?
Subscript and call: ops[2](a, b). The form (*ops[2])(a, b) is the same thing.
Is a function pointer table faster than a switch?
Not necessarily. Compilers can optimise switches into jump tables and inline cases. Choose a table for extensibility and measure speed on your own target.
How do I prevent out-of-range indexes?
Compute the length with sizeof table / sizeof table[0] and reject any index that is negative or not below that length.
Why should the table be const?
A const table is placed in read-only memory such as flash, saves RAM on microcontrollers and cannot be modified by a stray write.
Where is this pattern used in embedded C programming?
In command interpreters, state machines, protocol decoders and the interrupt vector table itself.
Free Embedded C Course Continues
Next: how to read complex pointer declarations without guessing.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments