Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Function Pointers Embedded Lab
Simulate an interrupt vector table with function pointers in embedded C, then test yourself with a quiz, coding challenges and interview questions.
This is the final lecture of the function pointer series in our free C programming course, and it ties everything together with a lab on function pointers in embedded C. You will build a miniature interrupt vector table that runs on your PC, understand how the same idea works on a real microcontroller, and then check your knowledge with a quiz, practice challenges and interview questions. It is a practical finish to this free embedded C course chapter and a useful step for anyone learning C programming for embedded systems.
What You Will Learn
- How a vector table of handler addresses drives interrupt handling
- How to build a const table with designated initialisers
- Why a default handler protects a system from unexpected events
- How to bounds-check an interrupt number before dispatch
- How to test yourself with a quiz and typical interview questions
Prerequisites
Complete the earlier lectures in this chapter: calling through pointers, callbacks, arrays of function pointers and reading declarations.
How a Real Vector Table Works
When a hardware event such as a timer overflow or a received UART byte occurs, the processor stops what it is doing and runs a small routine called an interrupt service routine, or ISR. The processor finds the routine by looking it up in a table of addresses stored in memory. On an ARM Cortex-M part, the first entry of that table holds the initial stack pointer and the second holds the reset handler, and the remaining entries are the addresses of exception and peripheral interrupt handlers.
Function pointers in embedded C make this possible, because that table is exactly an array of function pointers. Our lab reproduces the mechanism in ordinary C so you can run and study it without a board.
Lab: A Simulated Vector Table in C
The program defines three interrupt numbers. Two have handlers; the GPIO slot is deliberately left empty, and an out-of-range number is also fired to show the safety net. Designated initialisers, written as [IRQ_TIMER] = timer_isr, set specific slots by name; every unnamed slot automatically becomes NULL. Save the file as ep_vectors.c.
#include <stdio.h>
#include <stddef.h>
typedef void (*handler_fn)(void);
enum irq_id { IRQ_TIMER, IRQ_UART, IRQ_GPIO, IRQ_COUNT };
static unsigned int tick_count;
static unsigned int rx_bytes;
static void timer_isr(void)
{
tick_count++;
printf(" timer_isr : tick %u\n", tick_count);
}
static void uart_isr(void)
{
rx_bytes++;
printf(" uart_isr : received byte %u\n", rx_bytes);
}
static void default_isr(void)
{
printf(" default_isr : unexpected interrupt, ignored\n");
}
/* GPIO slot is intentionally left empty (NULL) */
static const handler_fn vectors[IRQ_COUNT] = {
[IRQ_TIMER] = timer_isr,
[IRQ_UART] = uart_isr,
};
static void fire(unsigned int n)
{
handler_fn h = default_isr;
if (n < IRQ_COUNT && vectors[n] != NULL)
h = vectors[n];
printf("fire(%u)\n", n);
h();
}
int main(void)
{
const unsigned int events[] = { IRQ_TIMER, IRQ_UART, IRQ_GPIO, 7, IRQ_TIMER };
for (size_t i = 0; i < sizeof events / sizeof events[0]; i++)
fire(events[i]);
printf("ticks = %u, rx = %u\n", tick_count, rx_bytes);
return 0;
}
Build and Run
gcc -Wall -Wextra -std=c11 -pedantic -o ep_vectors ep_vectors.c
./ep_vectors
Expected output:
fire(0)
timer_isr : tick 1
fire(1)
uart_isr : received byte 1
fire(2)
default_isr : unexpected interrupt, ignored
fire(7)
default_isr : unexpected interrupt, ignored
fire(0)
timer_isr : tick 2
ticks = 2, rx = 1
Walkthrough
handler_fnis a typedef for a function taking nothing and returning nothing, the usual shape of an ISR.firestarts withdefault_isras the safe choice, and replaces it only if the number is valid and the slot is not NULL.- Event 2 is the empty GPIO slot, and event 7 is out of range. Both reach the default handler instead of crashing.
- The timer fired twice and the UART once, which the final summary line confirms.
- The two counters are file-level variables. In real firmware, variables shared between an ISR and main code should be declared
volatile, and updates larger than the CPU’s word size need protection. This PC lab keeps things simple.
On real hardware
Vendor startup files fill the vector table for you. Unused entries typically alias a default handler, often with the GCC attribute weak, so your own function with the right name overrides it at link time. The design goal is the same as in our lab: no empty slot should ever lead to a random jump.
Common Mistakes with Function Pointers in Embedded C
| Mistake | Effect | Fix |
|---|---|---|
| Leaving vector slots NULL on real hardware | Hard fault or jump to address zero | Point every slot to a default handler |
| No range check on an event number | Reads beyond the table | Compare with the table length first |
| Long processing inside an ISR | Latency, lost events | Set a flag, handle it in the main loop |
Shared variables not volatile | Compiler caches a stale value | Mark shared data volatile |
| Writable table in RAM | Handler can be overwritten | Declare the table const |
Best Practices for Function Pointers in Embedded C
- Keep the table const, so it lives in flash and cannot be corrupted at run time.
- Give every handler the same typedef and a clear name such as
timer_isr. - Keep ISRs short; indirect dispatch costs a few cycles, which matters when events arrive often.
- Treat any externally supplied event or command number as untrusted and validate it.
Summary and Key Takeaways
- A vector table is an array of function pointers indexed by event number.
- Designated initialisers fill chosen slots; others become NULL.
- A default handler plus a bounds check makes dispatch safe.
- Declaration reading, typedefs and const tables keep function pointers in embedded C clean and safe.
Quiz: Function Pointers in Embedded C
Choose the best answer for each question. Open the answer below each one only after you commit to your choice.
What does
int (*f)(int);declare?- A. A function returning a pointer to int
- B. A pointer to a function taking an int and returning an int
- C. An array of function pointers
- D. A pointer to an int
Show answer
B. The parentheses bind the star to the name f.
If
fppoints toadd(int, int), which line is NOT a correct call?- A.
fp(1, 2) - B.
(*fp)(1, 2) - C.
*fp(1, 2) - D.
add(1, 2)
Show answer
C. It parses as
*(fp(1, 2)), trying to dereference an int result.- A.
On a 64-bit system, what is
sizeof(int *a[10])?- A. 8
- B. 10
- C. 40
- D. 80
Show answer
D. Ten pointers of 8 bytes each.
What does
typedef void (*cb_t)(void);create?- A. A variable named cb_t
- B. A function named cb_t
- C. A type name for pointers to functions taking no arguments and returning nothing
- D. An array type
Show answer
C. The name goes where the variable name would normally be.
What happens when you call a NULL function pointer?
- A. Nothing, the call is skipped
- B. The default function runs
- C. Undefined behaviour, often a crash or hard fault
- D. The compiler inserts a check
Show answer
C. Always initialise and check optional pointers.
What is
int (*p)[5]?- A. Array of 5 pointers to int
- B. Pointer to an array of 5 ints
- C. Function returning a pointer
- D. Pointer to 5 functions
Show answer
B. Parentheses force the star to bind to p first.
Which is the correct explicit call of element 2 of a table named
ops?- A.
(*ops)[2](a, b) - B.
(*ops[2])(a, b) - C.
*ops[2](a, b) - D.
ops(2)(a, b)
Show answer
B. Subscript first, then dereference. The plain form
ops[2](a, b)is equivalent.- A.
Why does our
firefunction start withdefault_isr?- A. It is the fastest handler
- B. It guarantees a valid function is called even for empty or invalid slots
- C. The compiler requires it
- D. It resets the processor
Show answer
B. A safe default prevents calls through NULL or out-of-range entries.
Which declaration is a function that returns a pointer to int?
- A.
int (*f)(void); - B.
int *f(void); - C.
int *(*f)(void); - D.
int (*f[2])(void);
Show answer
B. No parentheses around the star, so f is a function.
- A.
A qsort comparison callback should return:
- A. Only 0 or 1
- B. Negative, zero or positive
- C. A pointer to the larger element
- D. Nothing
Show answer
B. Negative means first before second, zero equal, positive after.
Practice Challenges
- Add a
gpio_isrhandler toep_vectors.c, register it in the table, and confirm that event 2 no longer reaches the default handler. - Extend
ep_dispatch.cwith a fifth operation,mod, that rejects a zero divisor, and check that the bounds check still adapts automatically. - Write a function
find_handler(const char *name)that searches the struct table inep_dispatch.cby name and returns the matching function pointer, or NULL if absent. Use a typedef for the return type. - Write a program that keeps a per-interrupt counter array and prints how many times each IRQ fired, including invalid ones.
- Declare, without a typedef, an array of three pointers to functions that take a float and return a pointer to char. Then rewrite it with typedefs.
Interview Questions on Function Pointers in Embedded C
1. What is a function pointer and why would you use one?
It is a variable holding the address of a function. It enables callbacks, run-time selection of behaviour, driver operation tables and interrupt vectors without hard-coding the target function.
2. 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 to a function returning int. The parentheses decide.
3. How would you implement a callback in C?
Define a typedef for the function pointer type, accept it as a parameter in the registering function, store or call it later, and check it for NULL first. Add a void pointer context argument when state is needed.
4. How is an interrupt vector table related to function pointers?
It is an array of handler addresses indexed by interrupt number. The hardware looks up the entry and jumps to it, which is the same as calling through an element of an array of function pointers.
5. Is a function pointer table faster than a switch statement?
Not necessarily. Compilers can turn switches into jump tables and inline cases. Tables win on extensibility and data-driven design; speed should be measured.
6. What can go wrong if you call a function through a pointer of the wrong type?
Undefined behaviour: arguments may be passed or read incorrectly, and the return value may be garbage. Compilers warn about incompatible pointer types, and recent ones error out.
7. Why should vector tables and dispatch tables be const?
Const data sits in read-only flash, saves RAM and cannot be overwritten by a stray pointer or exploit, which would otherwise redirect execution.
8. How do you read int *(*fp)(void)?
fp is a pointer to a function taking no arguments that returns a pointer to int.
9. How do you avoid unreadable function pointer declarations?
Use typedef to name the pointer type once, then declare variables, parameters and arrays with that name.
10. What should an ISR avoid doing?
Long or blocking work, heavy library calls and unprotected access to shared data. Keep it short, set a flag or queue an event, and mark shared variables volatile.
Conclusion
You have finished the function pointer topics of the pointers chapter. You can call through a pointer, pass one as a callback, build a safe dispatch table, decode complicated declarations and explain how an interrupt vector table works. These are core skills for anyone following this free C programming course towards embedded roles. The chapter ends here, and the next topics move on to strings, structures and bit-level work that firmware depends on.
Frequently Asked Questions
What are function pointers used for in embedded C?
They implement interrupt vector tables, driver interfaces, callbacks and state machines, so behaviour can be selected or replaced without changing the calling code.
How does an interrupt vector table use function pointers?
Each entry holds the address of a handler. When an interrupt occurs, the processor reads the entry for that number and runs the routine it points to.
Should ISRs be called through a pointer in my own code?
Normally the hardware calls them. In tests and simulations you may call them through the table, as this lab does.
Why use designated initialisers for a table?
They place each handler in a named slot, so the table stays correct even if the enumeration order changes.
Is this free C tutorial enough to prepare for interviews?
It covers the function pointer questions that appear most often. Combine it with the pointer, array and memory lectures of this free embedded C course for full preparation.
Do I need real hardware for this lab?
No. It runs on any computer with a C compiler. Later you can port the same idea to a microcontroller board.
Keep Going With the Free Embedded C Course
Explore the full free C programming course on EmbeddedPathashala and keep building real embedded skills.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

1 Comment