What are Call Functions Through Pointers- Free Embedded Systems and 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 55

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.

Function pointer and the code it points to
RAM: variable op holds address → 0x08000140
Flash: address 0x08000140 first instruction of → square()
op(7) jumps to → square(7) returns → 49

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 styleExampleNotes
By namesquare(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 dereferenceop(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.

Direct call versus indirect call
Direct main → square (target fixed at build time)
Indirect main → read op → jump to that address (target decided at run time)

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

MistakeSymptomFix
Forgetting the parentheses in int (*op)(int)Compiler error or a declared function instead of a pointerWrap the star and the name together
Writing op = square(7)Stores the result 49, not the address; type warningDrop the parentheses and arguments when taking the address
Mismatched parameter typesIncompatible pointer type warning, wrong results at run timeMake the pointer type match the prototype exactly
Calling before assigningCrash or hard faultInitialise at declaration; check against NULL
Printing a function pointer with %p without a castPedantic warningCast to void * for display only; never rely on it for arithmetic

Best Practices for Function Pointers in C

  • Create a typedef for any pointer type you use more than once; you will see this in the next lecture.
  • Declare static helper functions with static so their names do not leak out of the file; you can still take their address.
  • Compile with -Wall -Wextra and 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 qsort that 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) and op(7) give the same result when op points to square.
  • 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 = &square; 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

Leave a Reply

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