Function pointers are one of the most powerful yet misunderstood features in C especially in embedded firmware where performance, memory layout, and safety matter.
A function pointer holds the address of executable code and behaves like an ordinary pointer, except that it points to the program code region (Flash/ROM) instead of data memory (RAM).
Let’ see memory regions and placement for function pointers and targets.
Memory regions and placement:
Understanding where function pointers and their targets reside in memory is key to writing efficient, reliable embedded code. Below is a clear, compact guide showing what goes in each memory region, how function pointers behave, and practical rules to avoid common pitfalls.
Flash / ROM (Read-Only Memory):
Purpose: Stores compiled program code and constant data.
What goes here:
- All executable code (functions, methods).
- Constant tables and arrays declared with const.
- Read-only lookup tables or configuration data.
Access pattern: The CPU typically fetches instructions from Flash; data stored in Flash persists across power cycles but can be modified at runtime only through explicit erase and program operations that are slower, sector/page based, have limited endurance, and require special handling to ensure reliability.
RAM (Random-Access Memory):
Purpose: Holds mutable data that the CPU can read/write at runtime.
What goes here:
- Writable function-pointer variables.
- Mutable tables, buffers, and stacks.
- Global variables not declared const.
Access pattern: The CPU reads/writes data directly to RAM during execution. This memory is volatile and lost after power-down unless backed by battery or retention circuitry.
Startup Code and Interrupt Vector Tables:
Purpose: Provide the CPU with the addresses of the entry points for exception and interrupt handlers.
Placement requirements:
- Must reside at fixed, architecture-defined memory addresses. For example, on ARM Cortex-M, the vector table usually starts at address 0x00000000 (Flash) or can be relocated to RAM.
- Each entry is typically the address of a function or offset.
- Entries must exactly match the hardware layout expected by the CPU. A mismatch can cause undefined behavior or crashes.
Key Considerations:
Flash vs RAM execution: Some systems allow executing code from RAM for faster performance or to modify code at runtime. This requires copying Flash content into RAM at startup.
Function pointers:
- Const pointers: Placed in Flash if they are read-only.
- Writable pointers: Placed in RAM to allow runtime reconfiguration of behavior (like changing ISR handlers dynamically).
Memory alignment: Hardware often requires vector tables and function pointers to be aligned to word boundaries for correct operation.
How Function Pointers Work Internally:
Let’s dive into how function pointers work internally. To explain this clearly, we will refer to the ARM Cortex processor architecture. We will begin by exploring the memory representation of function pointers and how they interact with program code and data in embedded systems.
Memory Representation of Function Pointers:
A function pointer is essentially an address pointing to executable code (typically the function entry in Flash). On ARM Cortex-M the pointer width matches the data-pointer width (typically 32-bit) and function pointers are stored in RAM or registers unless placed in Flash via const.
When you assign a function name to a pointer, the compiler stores that function’s starting address.
Example,
void add( int a, int b)
{
return (a+b);
}
void (*pf)(void) = add; // 'pf' now holds the address of 'add()'
Here, the compiler resolves the symbol ‘add’ to its entry address in program memory and assigns it to ‘pf’.
+---------+ +-----------------+ | pf in | --> | Address of add()| | RAM | | in Flash | +---------+ +-----------------+
⚠️ Note: Unlike data pointers, you never dereference a function pointer as memory; the CPU executes the address as instructions.
Invocation Mechanism:
When a function pointer is called, e.g., pf(), the compiler emits an indirect branch instruction. On ARM Cortex-M (Thumb architecture):
BLX Rn ; Branch with link to the address in register Rn
- Rn contains the function pointer value.
- The CPU fetches the instruction from this address and begins execution.
- The Link Register (LR) stores the return address for proper function return.
This indirect execution allows runtime flexibility: callbacks, interrupt handlers, and RTOS hooks rely heavily on this mechanism.
┌──────────────────────────┐
│ Function Code in Flash │
│ Name: add() │
│ Address: 0x0800_1234 │
└───────────┬──────────────┘
│
▼
┌──────────────────────────┐
│ Function Pointer (pf) │
│ Stored in: RAM │
│ Value: 0x0800_1234 │
└───────────┬──────────────┘
│
▼
pf() → CPU executes Add() via BLX instruction
Thumb / ARM Low-Bit Semantics:
ARM uses the least significant bit (LSB) of function pointers to indicate the instruction set mode:
| LSB | Meaning | | --- | ---------- | | 0 | ARM mode | | 1 | Thumb mode |
On Cortex-M (Thumb-only cores), the LSB must always be 1. Stripping this bit (e.g., via unsafe casts) causes HardFaults.
Note: Always use the compiler-provided function pointer value without manual manipulation.
Common Embedded Use Cases:
- Callback functions – Peripheral drivers, timers, or ISRs.
- RTOS hooks – Task startup, idle tasks, or scheduler callbacks.
- Dynamic dispatch – Lightweight polymorphism without virtual functions.
- Bootloaders and firmware updates – Jumping to runtime-loaded images in flash or RAM.
🧩 FAQ Section:
Q1. Where are function pointers stored in embedded systems?
Function pointer variables are stored in RAM, while the code they point to resides in Flash or ROM.
Q2. Is a function pointer slower than a direct call?
Not significantly. It typically adds one indirect branch (BLX on ARM Cortex-M) — a single-cycle overhead.
Q3. Can function pointers be stored in Flash memory?
Yes, if declared as const. This saves RAM and is common for static lookup or vector tables.
Q4. How do ARM and IAR handle Thumb mode in function pointers?
On ARM Cortex-M, the least significant bit of the pointer indicates Thumb mode. The IAR compiler automatically preserves this when assigning function addresses.
Q5. Why are function pointers used in embedded systems?
They enable callbacks, event-driven architectures, state machines, HAL abstraction, and polymorphic interfaces — essential for modular firmware design.