Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 35

Declaring Function Pointer Variables

Recommended resources

Hello! Welcome to the first lesson in our module on "Function Pointers and Callbacks."

In our last lesson, we saw how command-line arguments are passed to a program via argc and argv. You learned that argv is an array of character pointers (char *argv[] or char **argv), a real-world example of the pointer-to-a-pointer concept. A key takeaway was how a program like git could use an argument like "commit" or "push" to decide which major piece of functionality to execute.

Today, we will begin learning about one of the most powerful mechanisms for implementing that kind of dynamic behavior: function pointers. Our learning outcome is to declare function pointer variables with the correct syntax. This lesson is all about mastering the notation, which can be tricky at first but is essential for everything that follows in this module.

What is a Function Pointer?

In C, everything in a running program has a memory address. You're familiar with variables having addresses in the data, stack, or heap segments. Functions are no different. The compiled machine code instructions for a function reside in a read-only area of memory called the code segment (or text segment). The name of a function, in essence, acts as a label for the starting address of its first instruction.

A function pointer is simply a special type of pointer variable designed to hold the memory address of a function. Instead of pointing to data, it points to executable code.

This concept is explained very clearly in the following video.

Function Pointers in C / C++

To start, watch this short clip from mycodeschool. It provides an excellent conceptual foundation by explaining that a function's address is simply the entry point to its block of instructions in memory.

Please watch from 05:08 to 06:25. Focus on the idea that a function name corresponds to the address of its first instruction.

By storing a function's address in a pointer, you can call that function indirectly through the pointer. This allows you to decide at runtime which function to execute, a technique that is fundamental for creating flexible and extensible software, such as implementing callbacks in communication middleware or building state machines.


The Declaration Syntax

Declaring a function pointer requires specifying three things:

  1. The return type of the function it can point to.
  2. The name of the pointer variable itself.
  3. The parameter types of the function it can point to.

This is often called the function pointer's signature. A function pointer can only point to functions that have a matching signature.

The general syntax looks like this:

return_type (*pointer_name)(parameter_types);

Let's break this down with a practical reading exercise.

Function Pointer in C – Master Syntax & Use Cases Fast!

The article 'Function Pointer in C' on upgrad.com provides a clear breakdown of the declaration syntax. This will give you a solid template to work from.

Please read the sections titled 'Syntax of Function Pointers in C' and 'Example: Basic Function Pointer Declaration'. Pay close attention to the role of each part of the declaration.

So, for a function with the prototype int add(int a, int b);, a corresponding function pointer named pfn_add would be declared as:

int (*pfn_add)(int, int);

  • int: This is the return type.
  • (*pfn_add): This declares pfn_add as a pointer. The parentheses are crucial.
  • (int, int): These are the parameter types.

The "Inside-Out" Rule and The Importance of Parentheses

The syntax int (*pfn_add)(int, int); can be confusing. A helpful way to parse complex C declarations is the "inside-out" or "right-left" rule. You start with the variable name and work your way out.

10.8. Function Pointers — Computer Systems Fundamentals

This resource from 'Computer Systems Fundamentals' offers a fantastic visual and mental model for understanding function pointer declarations.

Read the section that begins just after 'Code Listing A.50' and includes 'Figure 10.8.1: Components of a function pointer declaration'. Focus on the explanation of how to read the declaration from the 'inside to the outside'.

Applying this to int (*pfn_add)(int, int);:

  1. Start with the name: pfn_add...
  2. Look right: nothing until ). Look left: *. So, "...is a pointer..."
  3. Move outside the parentheses. Look right: (int, int). So, "...to a function that takes (int, int)..."
  4. Look left: int. So, "...and returns an int."

Putting it together: pfn_add is a pointer to a function that takes (int, int) and returns an int.

The parentheses around (*pfn_add) are mandatory and non-negotiable. Without them, the compiler's precedence rules change the meaning entirely.

Compare these two declarations:

  • int (*p)(int, int); // p is a pointer to a function.
  • int *p(int, int); // p is a function that returns a pointer to an int.

This critical distinction is a common source of bugs. The following video segment highlights this and other syntax details.

Function Pointers in C / C++

This next clip from mycodeschool clearly explains the ambiguity that arises without parentheses and reinforces the need for an exact signature match.

Please watch the following three segments: Declaration Syntax: (06:45 - 07:49) Syntax Ambiguities: (08:37 - 09:40) - This part directly addresses the int *f() vs. int (*f)() issue. Signature Matching: (10:11 - 10:57) - This shows why the return type and parameters must match perfectly.

As you saw, a function pointer is strongly typed. Attempting to assign a function with a mismatched signature will result in a compile-time error, a safety feature that is essential in the kind of high-integrity systems you build. This aligns with the principles of MISRA-C, which heavily restricts incompatible pointer type conversions.

Test your understanding!

Your ADAS system has a function that processes incoming radar data. Its prototype is:

int ProcessRadarFrame(const unsigned char* frame_buffer, size_t buffer_size, long long timestamp_ns);

Write the C declaration for a function pointer variable named g_radar_handler that could be used to point to this function.

Show answer

The declaration is:

int (*g_radar_handler)(const unsigned char*, size_t, long long);

Let's break it down:

  • int: The return type matches.
  • (*g_radar_handler): The variable is a pointer named g_radar_handler.
  • (const unsigned char*, size_t, long long): The parameter types match the function's signature exactly.

A Brief Look at Usage

While our focus today is on declaration, it's helpful to see how these pointers are assigned and used. This will be the main topic of our next lesson.

You assign the address of a function to a pointer by using the function's name. The & operator is optional, as a function's name decays to its address in this context.

// Function prototype
void PrintMessage(const char* msg);

// Function pointer declaration
void (*p_print)(const char*);

// Assignment
p_print = PrintMessage; // The name 'PrintMessage' provides the address
// OR
p_print = &PrintMessage; // Explicitly taking the address also works

A common mistake is to accidentally call the function during assignment.

10.8. Function Pointers — Computer Systems Fundamentals

Please read this short 'Bug Warning' from 'Computer Systems Fundamentals'. It highlights a very common error when initializing function pointers.

Read the 'Bug Warning' section that explains the difference between ptr = trouble; (correct) and ptr = trouble(); (incorrect).

Once assigned, you can call the function through the pointer. Again, there are two equivalent syntaxes:

// Calling the function
p_print("Hello from a function pointer!"); // Implicit dereference (more common)
(*p_print)("Hello again!");              // Explicit dereference

Conclusion

In this lesson, we've focused on the foundational syntax for working with function pointers. Mastering this notation is the first and most critical step toward using them effectively.

Key Takeaways:

  • A function pointer is a variable that stores the memory address of a function.
  • Functions reside in the code segment of memory, and their names act as labels for their starting addresses.
  • The declaration syntax is return_type (*pointer_name)(parameter_types);.
  • The parentheses around (*pointer_name) are mandatory to distinguish a function pointer declaration from a function prototype that returns a pointer.
  • A function pointer's signature (return type and parameter types) must exactly match the signature of any function it points to, ensuring type safety.

Preview of the Next Lesson:
Now that you can correctly declare a function pointer, our next lesson will dive deeper into assigning function addresses and invoking functions through them. We will explore practical examples of how to swap which function gets called at runtime, laying the groundwork for implementing powerful patterns like callbacks.