Reading Function Pointer Declarations- Free Online C Programming Course

Need Help or More Information?

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

Reading Function Pointer Declarations

Decode any C function pointer declaration with a simple three-step method. Part of the free C programming course for embedded systems.

free c programming coursefree c tutorialc programming for embedded systemsembedded c programmingfree embedded c course

What You Will Learn in This Lecture

  • Why function pointer declarations in C look harder than they are
  • A three-step method that decodes any declaration, however nested
  • The difference between a function that returns a pointer and a pointer to a function
  • How to declare arrays of function pointers and call through them
  • How typedef turns an unreadable declaration into a one-word type
  • Where this appears in embedded C programming: callbacks, command tables, interrupt vectors

Prerequisites

You should be comfortable with basic pointer declaration, the address-of and dereference operators, and how functions return values. If you are following this free C programming course in order, the previous lectures in the Pointers chapter cover everything needed.

Why Function Pointer Declarations in C Look Confusing

Every function pointer declaration is built from only four ingredients: an identifier, a *, a pair of parentheses holding parameters, and a return type. The trouble is not the ingredients but the **precedence**. In a declarator, the function-call parentheses () and the array brackets [] bind tighter than *. So int *f(void) means “f is a function returning int *“, not “f is a pointer to a function”. To get the second meaning you must force the * to bind to the name first by wrapping them: int (*f)(void).

This single rule explains almost every confusing line you will meet in a free C tutorial or in real embedded firmware. Once you accept that the parentheses around *name are doing the job of “bind this to a pointer first”, the rest is mechanical.

A Three-Step Method to Read Any Declaration

  1. **Find the name.** Start at the identifier. Everything is read outward from it.
  2. **Go right, then left.** Read what sits immediately to the right of the name (() means “function taking…”, [N] means “array of N…”). Only when the right side is exhausted or closed by a ) do you move left to read * as “pointer to”.
  3. **Parentheses first.** If the name is inside a pair of parentheses, finish the inside completely, then step outside and continue with the right side, then the left side.

Decoding int *(*getp)(void) step by step

Step 1 getp → the name is getp
Step 2 (*getp) → getp is a pointer to …
Step 3 (void) → … a function taking no arguments …
Step 4 int * → … that returns a pointer to int

Read in one breath: **getp is a pointer to a function that takes no arguments and returns a pointer to int.**

Five Declarations Decoded

The table below covers the shapes you meet most often. Change the names and parameter lists and the same reading method still works.

DeclarationPlain-English reading
int (*cb)(void);cb is a pointer to a function with no arguments that returns int
int (*cb_table[4])(void);cb_table is an array of 4 pointers, each to a function with no arguments returning int
int *(*getp)(void);getp is a pointer to a function with no arguments that returns an int pointer
float *(*mk)(int, float);mk is a pointer to a function taking an int and a float and returning a float pointer
float *(*mk_tab[4])(int, float);mk_tab is an array of 4 pointers, each to a function taking int and float and returning a float pointer

Where the array brackets sit

int (*cb)(void) one pointer → one function
int (*cb_table[4])(void) cb_table[0]cb_table[1]cb_table[2]cb_table[3] → four pointers

Tip: In cb_table[4] the brackets are inside the parentheses, right next to the name. That is why the array is of pointers. Move the brackets outside and the declaration stops being valid, because C does not allow an array of functions.

Pointer to Function vs Function Returning a Pointer

This is the mistake that costs beginners the most time. Compare two declarations that differ by one pair of parentheses.

DeclarationWhat it isCan you assign get_slot to it?
int *p(void);a **function** named p returning int *No, it is a prototype, not a variable
int (*p)(void);a **pointer variable** that can hold the address of such a functionYes: p = get_slot;

Remember: a function name used in an expression already decays to its address, exactly as an array name decays to the address of its first element. So p = get_slot; and p = &get_slot; are equivalent, and p() and (*p)() call the same function.

Complete Example: Declaring and Calling Through Pointers

The program below declares one pointer for each shape from the table, plus an array of three pointers. The functions are small stand-ins for something you would see in embedded C programming: reading a sensor, fetching a configuration slot, and scaling a raw ADC count.

#include <stdio.h>

static int    read_sensor(void)            { return 42; }
static int   *get_slot(void)               { static int slot = 7; return &slot; }
static float *scale(int raw, float gain)   { static float out; out = (float)raw * gain; return &out; }

int main(void)
{
    int    (*p_read)(void)          = read_sensor;
    int   *(*p_slot)(void)          = get_slot;
    float *(*p_scale)(int, float)   = scale;
    int    (*table[3])(void)        = { read_sensor, read_sensor, read_sensor };

    printf("read   : %d\n", p_read());
    printf("slot   : %d\n", *p_slot());
    printf("scaled : %.2f\n", *p_scale(10, 0.5f));

    int sum = 0;
    for (int i = 0; i < 3; i++)
        sum += table[i]();
    printf("table  : %d\n", sum);
    printf("sizeof(table) = %zu, sizeof(table[0]) = %zu\n", sizeof table, sizeof table[0]);
    return 0;
}

Build and run it. The -std=c17 flag works on any recent GCC or Clang, and the code also compiles under C23.

gcc -std=c17 -Wall -Wextra -o fp_decl fp_decl.c
./fp_decl

Expected output on a 64-bit Linux machine:

read   : 42
slot   : 7
scaled : 5.00
table  : 126
sizeof(table) = 24, sizeof(table[0]) = 8

Three things to notice. First, p_slot() returns an int *, so the result is dereferenced with * before printing. Second, *p_scale(10, 0.5f) parses as *(p_scale(10, 0.5f)): the call happens first, then the dereference. Third, sizeof(table) is 24 because three pointers of 8 bytes each sit in the array; on a 32-bit microcontroller it would be 12.

Warning: Both get_slot and scale return the address of a static object. Returning the address of an ordinary local variable would hand the caller a dangling pointer. The static keeps the storage alive after the function returns.

Making Declarations Readable with typedef

When a function pointer type appears in more than one place, name it once. The typedef below captures the whole shape, and the array declaration becomes trivial to read.

#include <stdio.h>

typedef int (*op_fn)(void);

static int one(void) { return 1; }
static int two(void) { return 2; }

int main(void)
{
    op_fn ops[2] = { one, two };
    printf("%d %d\n", ops[0](), ops[1]());
    return 0;
}
1 2

The name op_fn now stands for “pointer to a function taking no arguments and returning int“. Most production embedded code, including vendor HAL libraries, uses exactly this style for callbacks.

Real-World Uses in Embedded C Programming

  • **Interrupt vector tables:** an array of pointers to handler functions, indexed by interrupt number.
  • **Command dispatchers:** a UART shell maps the typed command to a function pointer in a lookup table.
  • **Driver ops tables:** a device exposes a struct of function pointers (open, read, write) so upper layers stay hardware independent.
  • **State machines:** each state is a function, and the current state is a single function pointer.

Command dispatch table

index 0 “led_on” → cmd_led_on()
index 1 “led_off” → cmd_led_off()
index 2 “status” → cmd_status()

Common Mistakes and Troubleshooting

MistakeSymptomFix
Missing parentheses: int *f(void) when a pointer was meantWarning about initialization from incompatible type, or a “function redeclared” errorWrite int (*f)(void)
Calling a pointer that is still NULLSegmentation fault at the callCheck if (cb != NULL) before calling
Wrong parameter list in the pointer typeCompiler warning about incompatible pointer type; undefined behaviour if ignoredMake the pointer type match the function signature exactly
Writing fp = func() instead of fp = funcAssigns the return value, not the addressDrop the parentheses when taking the address

Best Practices, Performance and Security

  • Use a typedef the moment a function pointer type is used twice.
  • Prefer const tables: static int (* const handlers[])(void) keeps the table in flash on a microcontroller and stops accidental overwrites.
  • An indirect call costs a load plus an indirect branch and cannot be inlined, so avoid it in the innermost loop of timing-critical code.
  • Treat any writable function pointer as a security-sensitive object: an attacker who can overwrite it controls where execution goes. Keep tables const and validate indexes before dispatch.
  • Always initialise function pointers, either to a real function or to NULL, and check before calling.

Summary and Key Takeaways

  • () and [] bind tighter than *, so (*name) is needed to declare a pointer to a function.
  • Read a declaration from the name outward: right first, then left, parentheses first.
  • int *p(void) is a function; int (*p)(void) is a pointer to a function.
  • name[N] inside the parentheses makes an array of function pointers.
  • A typedef removes nearly all the visual noise.

Conclusion

Function pointer declarations stop being intimidating once you stop reading them left to right and start reading them from the name outward. Practise the three-step method on every declaration you meet in header files and vendor libraries; after a week it becomes automatic. In the next lecture of this free embedded C course we move to a different pointer hazard: pointers that are used before they point anywhere valid.

Frequently Asked Questions

What is a function pointer in C?

A function pointer is a variable that stores the address of a function. You can call the function through it, pass it to other functions as a callback, or keep many of them in an array.

How do I read a complicated function pointer declaration?

Start at the identifier, read right (parameters or array size), then left (* means pointer to, the type on the far left is the return type), and always finish anything inside parentheses first.

What is the difference between int *f(void) and int (*f)(void)?

The first declares a function returning a pointer to int. The second declares a pointer variable that can point to a function returning int.

Do I need the ampersand when assigning a function to a pointer?

No. A function name already converts to its address, so fp = func; and fp = &func; mean the same thing.

Can I make an array of function pointers?

Yes, for example int (*table[4])(void);. An array of functions themselves is not allowed in C, only an array of pointers to them.

Why are function pointers common in embedded C programming?

They give you callbacks, interrupt vector tables, command dispatch and driver interfaces without needing an operating system or an object model.

Keep Learning Embedded C for Free

Continue the free C programming course with the next pointer lecture and test yourself on every topic.

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 *