What are Function Pointer Arrays in C- Free C Programming Course

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Free C Programming Course · Pointers · Lecture 57

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 sizeof so 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.

Dispatch table: index selects the function
Index 0 → op_add
Index 1 → op_sub
Index 2 → op_mul
Index 3 → op_div
Index 9 → rejected by bounds check

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

  1. Each row of script holds a command index and two operands.
  2. OP_COUNT divides the table’s total size by one element’s size, giving 4.
  3. The check idx < 0 || (size_t)idx >= OP_COUNT rejects invalid values before they touch the table. The cast keeps the comparison between unsigned values.
  4. 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.
  5. 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.

AspectDispatch tableSwitch statement
Adding a commandAdd one rowEdit the switch body
Data-drivenYes, table can be const data or even loaded at run timeNo, logic is compiled in
InliningNot possible across the indirect callPossible
Readability for few casesOverkillClear
Readability for many casesCompact and regularLong
SpeedConstant-time lookup; measure on your targetOften 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

MistakeResultFix
No bounds check on the indexOut-of-range read, jump to random addressCompare against the table length first
Hard-coding the lengthTable and check drift apartUse sizeof table / sizeof table[0]
Leaving an entry NULLCrash on callFill every slot or test for NULL, and use a default handler
Handlers with different signaturesIncompatible pointer errorsMake all handlers share one typedef
Table not constLives in RAM and can be overwrittenDeclare 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 const table.
  • 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

Leave a Reply

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