Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Call Functions Through Pointers
Learn how function pointers in C store a function’s address, how to call through them three ways, and why the type must match exactly. Part of the free embedded C course on EmbeddedPathashala.
Every function you write lives somewhere in memory, and function pointers in C let you hold that location in a variable and jump to it later. In this lecture of our free C programming course you will learn to store a function’s address, call it through the pointer, and spot the type mismatches that quietly break programs. If you are following this free C tutorial to prepare for firmware work, this is the first step towards drivers, callbacks and interrupt tables in embedded C programming.
What You Will Learn
- What a function name really is, and why it can be stored in a pointer variable
- Three equivalent ways of calling a function through a function pointer
- Why the pointer’s type must match the function’s signature exactly
- How to build and run a small test program and read its output
- Common mistakes with function pointers in C, including calling a NULL pointer
- Why this matters for C programming for embedded systems
Prerequisites
You should be comfortable with pointer declaration, the address-of operator and dereferencing from the earlier lectures of this chapter. You also need to know how to write a function with a prototype. Any C11 compiler works; the examples here were built with GCC 13 on Linux, but the same code compiles with arm-none-eabi-gcc for microcontrollers.
A Function Name Is an Address
When the compiler translates your program, every function becomes a block of machine instructions placed in the code area of memory. On a microcontroller this area is usually flash. The name of the function is simply a label for the first instruction of that block. In an expression, the name automatically turns into the address of that first instruction, in the same way an array name turns into the address of its first element.
That means a variable can hold it. A variable that holds the address of a function is called a function pointer, and it has a type that records which kind of function it may point to: the return type and the parameter types.
The address shown above is only an illustration. The real value is decided by the linker, and you should never hard-code it.
Declaring and Assigning a Function Pointer
The declaration mirrors the function’s prototype, with the name wrapped in parentheses and prefixed by a star. For a function that takes an int and returns an int:
int (*op)(int); /* op is a pointer to a function (int) returning int */
op = square; /* store the address of square() in op */
The parentheses around *op are essential. Without them you would declare a function named op that returns a pointer to int, which is a completely different thing. We study that trap in detail in the declaration-reading lecture later in this chapter.
The function must be declared before you take its address. If the compiler has not seen a prototype or a definition, it cannot know the function’s type, and it will either warn or refuse. Putting prototypes in a header, or defining small helpers above main as we do here, solves the problem.
Three Ways to Call Through a Function Pointer
Once op holds the address of square, you have three spellings that do the same job.
| Call style | Example | Notes |
|---|---|---|
| By name | square(7) | Direct call; the compiler knows the target at build time |
| Explicit dereference | (*op)(7) | Classic style; parentheses are needed because the call operator binds tighter than the star |
| Implicit dereference | op(7) | Modern and most common; the language converts it for you |
Both pointer forms produce identical machine code. Pick one style and use it consistently across a project. Many embedded teams prefer op(7) because it reads like a normal call.
Complete Example: Calling Function Pointers in C
Save the following as ep_fp_call.c. It calls the same function all four ways, compares the pointer with the function name, and prints how many bytes the pointer occupies.
#include <stdio.h>
static int square(int x)
{
return x * x;
}
int main(void)
{
int (*op)(int) = square;
printf("square(7) = %d\n", square(7));
printf("(*op)(7) = %d\n", (*op)(7));
printf("op(7) = %d\n", op(7));
printf("op == square : %s\n", (op == square) ? "yes" : "no");
printf("sizeof(op) = %zu bytes\n", sizeof op);
return 0;
}
Build and Run
gcc -Wall -Wextra -std=c11 -o ep_fp_call ep_fp_call.c
./ep_fp_call
Expected output on a 64-bit Linux machine:
square(7) = 49
(*op)(7) = 49
op(7) = 49
op == square : yes
sizeof(op) = 8 bytes
All three calls print 49 because they reach the same instructions. The comparison prints yes because op holds exactly the address that square evaluates to. On a 32-bit Cortex-M part the size line would show 4 bytes, since function pointers there are 32 bits wide.
Why the Type Must Match
A function pointer’s type is a promise about how to pass arguments and read the result. If you store a function with a different signature, the generated call uses the wrong convention, and the result is undefined behaviour. Floats may travel in different registers than integers, and the return value may be read from the wrong place. Look at this deliberately wrong program, ep_fp_mismatch.c:
int (*op)(int) = half; /* half() takes and returns float */
Compiling with warnings enabled gives a clear diagnostic:
ep_fp_mismatch.c: In function 'main':
ep_fp_mismatch.c:10:22: warning: initialization of 'int (*)(int)' from incompatible pointer type 'float (*)(float)' [-Wincompatible-pointer-types]
10 | int (*op)(int) = half; /* wrong: half takes and returns float */
| ^~~~
With GCC 13 this is a warning, so the program still builds and then misbehaves. Newer compilers, starting with GCC 14, treat it as a hard error by default. Never silence this warning with a cast just to make it go away; fix the signature instead.
Warning: do not call a NULL function pointer
An uninitialised or NULL function pointer sends the processor to address zero or to garbage. On a PC this is a segmentation fault. On a microcontroller address zero often holds the initial stack value from the vector table, so the CPU may run random data as code and lock up. Always initialise function pointers and test for NULL before calling when the pointer is optional.
Common Mistakes with Function Pointers in C
| Mistake | Symptom | Fix |
|---|---|---|
Forgetting the parentheses in int (*op)(int) | Compiler error or a declared function instead of a pointer | Wrap the star and the name together |
Writing op = square(7) | Stores the result 49, not the address; type warning | Drop the parentheses and arguments when taking the address |
| Mismatched parameter types | Incompatible pointer type warning, wrong results at run time | Make the pointer type match the prototype exactly |
| Calling before assigning | Crash or hard fault | Initialise at declaration; check against NULL |
Printing a function pointer with %p without a cast | Pedantic warning | Cast to void * for display only; never rely on it for arithmetic |
Best Practices for Function Pointers in C
- Create a
typedeffor any pointer type you use more than once; you will see this in the next lecture. - Declare static helper functions with
staticso their names do not leak out of the file; you can still take their address. - Compile with
-Wall -Wextraand treat incompatible-pointer warnings as errors. - Prefer
op(7)over(*op)(7)unless your coding standard says otherwise.
Performance Considerations
An indirect call costs a little more than a direct one, because the processor must load the address first and cannot always predict the target. The compiler also cannot inline a function it does not know at build time. For a handful of calls this is irrelevant. Inside a tight loop that runs a million times, measure before deciding.
Security Considerations
A function pointer stored in writable memory is an attractive target. If a buffer overflow overwrites it, the attacker chooses where your program jumps next. Keep tables of function pointers const so they live in read-only memory, validate any index used to select one, and never build a function pointer from external input.
Real-World Uses
- Interrupt handlers stored in a vector table on every ARM Cortex-M microcontroller
- Driver structures that bundle read, write and init operations for a device
- Sorting routines such as
qsortthat accept a comparison function - Menu systems and command parsers on serial consoles
Summary and Key Takeaways
- A function name evaluates to the address of its first instruction.
- Function pointers in C store that address and carry the function’s signature in their type.
square(7),(*op)(7)andop(7)give the same result whenoppoints tosquare.- The pointer type must match the function exactly or behaviour is undefined.
- Always initialise, and check for NULL when the pointer is optional.
Conclusion
You now know that a function is just code at an address, and that function pointers in C are typed variables holding that address. This idea is the foundation of everything that follows in this free embedded C course: passing behaviour to other functions, building dispatch tables, and writing interrupt vectors. In the next lecture you will pass a function pointer as an argument, which is how callbacks work.
Frequently Asked Questions
What are function pointers in C?
A function pointer is a variable that stores the address of a function. Its type records the return type and parameter types of the functions it may point to, so the compiler can generate a correct call.
Is op(7) the same as (*op)(7)?
Yes. Both call the function that op points to. The second form dereferences explicitly, the first lets the language do it. They generate identical code.
Can I take the address of a function with the & operator?
Yes. op = □ and op = square; are equivalent. Most programmers omit the ampersand.
Why do I need parentheses around *op in the declaration?
Without them, the declaration reads as a function returning a pointer. The parentheses bind the star to the name so it is read as a pointer to a function.
What happens if the signatures do not match?
The behaviour is undefined. The call may pass arguments in the wrong registers or read a wrong return value. Modern compilers warn or error on the assignment.
Are function pointers used in embedded C programming?
Constantly. Interrupt vector tables, driver operation tables, state machines and callback registration all rely on them.
How many bytes does a function pointer occupy?
It matches the platform’s address size: typically 8 bytes on 64-bit desktops and 4 bytes on 32-bit microcontrollers. Use sizeof to be sure.
Continue the Free C Programming Course
Keep learning embedded C programming for free. Next up: passing function pointers as arguments to build callbacks.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments