Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Declaring Function Pointers in C
Lecture 54 of the free C programming course: declare, assign and call a function pointer in C, understand why the parentheses matter, then test yourself with a quiz and interview questions.
A function pointer in C is a variable that stores the address of a function, so the function it refers to can be chosen while the program runs. In the previous lecture you learned that every function has an address. Here you learn the syntax to keep that address in a variable, which is the heart of callbacks, driver tables and state machines in embedded C programming.
This is the final lecture of this part of the free embedded C course, so it ends with a quiz and interview questions that you can use to prepare for real C programming for embedded systems interviews.
What You Will Learn
- The general syntax for declaring a function pointer in C
- Why the pointer name must be wrapped in parentheses
- How to assign a function address and call the function through the pointer
- How
typedefmakes function pointer declarations readable - What the compiler says when the signatures do not match
- A 10-question quiz and 10 interview questions with answers
Prerequisites
Complete cp8_53 (function addresses) first. You should also know how to declare and call a normal function, and how pointer declarations such as int *p are read. Everything is available in this free C tutorial.
Function Pointer Syntax in C
The general form is:
return_type (*pointer_name)(parameter_type1, parameter_type2, ...);
It looks like a function declaration, but the name is preceded by a star and the pair is wrapped in parentheses. Three small examples:
int (*op)(int, int); /* points to a function taking two ints, returning int */
void (*callback)(void); /* points to a function with no arguments and no result */
float (*convert)(int); /* points to a function taking an int, returning float */
Anatomy of a function pointer declaration
Why the Parentheses Matter
In C the function-call parentheses bind tighter than the star. Take the parentheses away and the meaning flips completely:
| Declaration | Meaning |
|---|---|
float (*fp)(int); | fp is a pointer to a function that takes an int and returns a float |
float *fp(int); | fp is a function that takes an int and returns a pointer to float |
int (*f)(void); | f is a pointer to a function with no parameters returning int |
int *f(void); | f is a function with no parameters returning pointer to int |
A simple reading method: start at the name, move right until you meet a closing parenthesis, then go left, and repeat. For float (*fp)(int) that reads “fp is a pointer (*) to a function (int) returning float”.
One pointer variable, two possible targets
Assigning an Address and Calling Through It
After declaring a pointer, store a function address in it using the function name alone. The function must have the same return type and parameter types as the pointer expects. Then call it in either of two equivalent ways: op(6, 3) or (*op)(6, 3).
Complete Example: Choosing an Operation
Save the following as fp_basics.c.
#include <stdio.h>
static int add(int a, int b) { return a + b; }
static int sub(int a, int b) { return a - b; }
typedef int (*binop_fn)(int, int);
int main(void)
{
int (*op)(int, int); /* pointer to function(int,int) -> int */
op = add;
printf("op = add -> op(6, 3) = %d\n", op(6, 3));
op = sub;
printf("op = sub -> (*op)(6, 3) = %d\n", (*op)(6, 3));
binop_fn same = add; /* same idea, readable with typedef */
printf("typedef version = %d\n", same(10, 20));
printf("sizeof op = %zu bytes\n", sizeof op);
printf("sizeof(int (*)(int,int)) == sizeof(void *) ? %s\n",
sizeof op == sizeof(void *) ? "yes" : "no");
return 0;
}
Build and Run
$ gcc -std=c11 -Wall -Wextra -Wpedantic -o fp_basics fp_basics.c
$ ./fp_basics
op = add -> op(6, 3) = 9
op = sub -> (*op)(6, 3) = 3
typedef version = 30
sizeof op = 8 bytes
sizeof(int (*)(int,int)) == sizeof(void *) ? yes
The same variable op ran two different functions, just by changing its value. The size line shows that on a 64-bit Linux machine a function pointer is 8 bytes. On a 32-bit microcontroller it is usually 4 bytes, but the C standard does not promise that it equals the size of a data pointer, so do not rely on it.
Making Declarations Readable with typedef
The line typedef int (*binop_fn)(int, int); creates a type name. From then on binop_fn same = add; reads almost like English. Real projects use typedefs for every callback type, for example one for a UART receive handler and another for a timer expiry handler.
What Happens If the Signature Does Not Match?
The compiler compares the function type with the pointer type. Suppose you try this:
static float half(int x) { return x / 2.0f; }
int (*wrong)(int, int) = half; /* different parameters and return type */
GCC reports:
warning: initialization of 'int (*)(int, int)' from incompatible pointer type
'float (*)(int)' [-Wincompatible-pointer-types]
Recent GCC versions (14 and newer) turn this into an error by default, which is the right behaviour. Calling a function through a pointer of the wrong type is undefined behaviour, so never silence the warning with a cast just to make the compiler quiet.
Real-World Uses in Embedded C Programming
- Callbacks: a timer driver accepts a
void (*cb)(void)and calls it on expiry. - Driver operation tables: a struct of function pointers such as open, read and write that different devices fill in differently, the same pattern Linux drivers use.
- Command dispatch: the command code received over a serial link selects the handler.
- Runtime selection: choose a filter or checksum routine based on configuration, without a long chain of if statements.
Common Mistakes with a Function Pointer in C
| Mistake | Result | Fix |
|---|---|---|
Missing parentheses around *name | Declares a function returning a pointer | Write (*name) |
op = add(2, 3); | Stores the call result, not the address | Write op = add; |
| Calling an uninitialised or NULL pointer | Crash or hard fault | Initialise and check before calling |
| Wrong parameter or return types | Undefined behaviour | Make the types match exactly |
| Casting away a warning | Hidden stack or register corruption | Fix the signature instead |
Warning: a function pointer that can be overwritten by a buffer overflow is a classic route to running attacker-chosen code. Keep dispatch tables const so they stay in flash, validate any index that selects a handler, and always check for NULL before a call.
Best Practices and Performance
- Create a
typedeffor every callback type, and name it after its purpose. - Initialise function pointers to NULL or to a safe default handler.
- Pass a
void *user-context argument along with callbacks so one handler can serve many devices. - An indirect call is slightly slower than a direct one and cannot always be inlined, so avoid it in tight inner loops.
- Prefer a table of function pointers over a long switch when the set of handlers is large and regular.
Putting a Function Pointer in C to Work
To see why this matters, picture a small firmware that supports two sensor types. Instead of testing the sensor type everywhere in the code, you store one function pointer in C that refers to the correct read routine, assign it once at start-up, and call it from the main loop. Adding a third sensor later means writing one new function and one new assignment, not editing every place that reads a value. The same idea scales up to a table of pointers, which later lectures use for command handlers.
Summary and Key Takeaways
- Declare with
return_type (*name)(params);and keep the parentheses. - Assign using only the function name; the signature must match.
- Call with
name(args)or(*name)(args); both are equivalent. typedefmakes callback types readable and reusable.- Check for NULL, match signatures, and protect tables from overwriting.
You now know how to declare and use a function pointer in C. With the earlier lectures of this free C programming course you can build callback-driven, table-driven embedded programs. Test yourself below.
Frequently Asked Questions
What is a function pointer in C?
A variable that holds the address of a function. You can call the function through it, and change which function it refers to at run time.
How do I declare a function pointer in C?
Use return_type (*name)(parameter types). For example int (*op)(int, int) points to functions taking two ints and returning int.
Do I write op(2, 3) or (*op)(2, 3)?
Both work and mean the same thing. The shorter form is more common.
Why is typedef recommended with function pointers?
It gives the declaration a short name, which makes arrays of pointers, struct members and function parameters much easier to read.
What happens if I call a NULL function pointer?
It is undefined behaviour. On most systems it crashes the program, and on a microcontroller it usually triggers a hard fault.
Can a function pointer have a different size from a data pointer?
Yes, the C standard allows it. On common platforms they are equal, but some architectures with separate code and data spaces use different widths.
Quiz: Test Yourself
Try each question first, then open the answer.
What does
float (*fp)(int);declare?- A) A function returning a pointer to float
- B) A pointer to a function taking an int and returning float
- C) An array of float pointers
- D) A pointer to a float
Show answer
B. The parentheses make fp a pointer, and the rest describes the function it points to.
What does
float *fp(int);declare?- A) A pointer to a function
- B) A float variable
- C) A function taking an int and returning a pointer to float
- D) A syntax error
Show answer
C. Without parentheses around *fp, the call parentheses bind first, so fp is a function.
Given
int add(int, int);andint (*p)(int, int);, which statement stores the address of add in p?- A) p = add();
- B) p = add;
- C) *p = add;
- D) p = add(int, int);
Show answer
B. The function name without parentheses is its address.
After
p = add;, which calls are valid and equivalent?- A) p(2, 3) only
- B) (*p)(2, 3) only
- C) Both p(2, 3) and (*p)(2, 3)
- D) Neither
Show answer
C. Both forms call the function.
Which function can be assigned to
void (*cb)(void);?- A) int f(void)
- B) void f(int)
- C) void f(void)
- D) void f(void *)
Show answer
C. Only the exact return type and parameter list match.
On a typical 64-bit Linux system, what is
sizeofa function pointer?- A) 2 bytes
- B) 4 bytes
- C) 8 bytes
- D) The size of the function body
Show answer
C. It stores an address, not the code. The code size does not matter.
What does
typedef int (*op_t)(int, int);create?- A) A variable named op_t
- B) A function named op_t
- C) A type name for pointers to functions of that signature
- D) A macro
Show answer
C. You can then write op_t handler = add;.
What is the likely result of calling a function pointer that is NULL?
- A) It calls main
- B) Nothing happens
- C) Undefined behaviour, usually a crash or hard fault
- D) It returns 0
Show answer
C. Always check before calling.
Which output does this print?
int twice(int x) { return 2 * x; } int (*p)(int) = twice; printf("%d", p(4) + (*p)(1));- A) 6
- B) 8
- C) 10
- D) 12
Show answer
C. p(4) is 8 and (*p)(1) is 2, so the sum is 10.
Why do we print function addresses with uintptr_t and PRIxPTR instead of %u?
- A) %u is slower
- B) An address may not fit in an unsigned int on a 64-bit system
- C) %u prints in binary
- D) There is no difference
Show answer
B. A 64-bit address is larger than a 32-bit unsigned int.
Interview Questions on Function Pointers
These come up often in embedded C interviews. Practise answering them aloud before opening the answers.
What is a function pointer, and where do you use it in embedded systems?
Show answer
It stores the address of a function. Typical uses are interrupt vector tables, driver callbacks, command dispatch tables and runtime selection of algorithms.
What is the difference between
int (*p)(int)andint *p(int)?Show answer
The first is a pointer to a function taking int and returning int. The second declares a function p that returns a pointer to int. The parentheses change the meaning because the call operator binds tighter than the star.
How do you read a complex declaration?
Show answer
Start at the identifier, go right until a closing parenthesis, then left, and repeat outward. Then express each step in words.
Is there any difference between
fnand&fnfor a function?Show answer
No. Both yield the address of the function, and both can be assigned to the same pointer type.
How does an interrupt vector table relate to function pointers?
Show answer
The table is an array of handler addresses in flash. On an interrupt the hardware reads the entry for that source and jumps to it, which is effectively a call through a function pointer.
Why would you use typedef with function pointers?
Show answer
For readability and consistency. It names the callback type once and avoids repeating a hard-to-read declaration in structs, parameters and arrays.
What happens when you call a NULL or uninitialised function pointer, and how do you protect against it?
Show answer
Undefined behaviour, typically a crash or hard fault. Initialise pointers, check for NULL before calling, or assign a safe default handler.
Can you convert a function pointer to void *?
Show answer
ISO C does not guarantee it, although POSIX systems support it. For portable code keep function pointers as function pointer types, and cast between function pointer types only when you will convert back before calling.
How would you design a callback registration API for a driver?
Show answer
Define a typedef such as void (*rx_cb_t)(void *ctx, const uint8_t *data, size_t len), and a function register_rx(rx_cb_t cb, void *ctx). The driver stores both and calls cb(ctx, data, len) when data arrives. The context pointer lets one handler serve many instances.
What are the security risks of function pointers, and how do you reduce them?
Show answer
Overwriting a pointer can redirect execution. Keep tables const in read-only memory, validate indices used to select handlers, check for NULL, avoid exposing pointers in structs next to unchecked buffers, and enable compiler hardening options.
Finish Strong with the Free C Programming Course
You have completed another part of the free embedded C course. Keep practising, revisit the quiz, and move on to the next lecture on EmbeddedPathashala.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments