Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I want to show you something that tripped me up for a good hour early in my career. You're trying to build a flexible system—maybe a plugin architecture or a simple callback—and you decide to use function pointers. You write what looks like perfectly reasonable C code, but the compiler starts screaming at you about types, or worse, it compiles but crashes the moment you try to execute the call.

// The Goal: A function that filters an array based on a custom rule
void filter_array(int *arr, int size, int *predicate(int)) {
    for (int i = 0; i < size; i++) {
        if (predicate(arr[i])) {
            printf("%d passed the test!\n", arr[i]);
        }
    }
}

int is_even(int n) {
    return n % 2 == 0;
}

int main() {
    int my_nums[] = {1, 2, 3, 4, 5};
    filter_array(my_nums, 5, is_even);
    return 0;
}

The Return Type Confusion

If you look closely at the filter_array signature, you'll see the mistake. I wrote int *predicate(int). To a human, that looks like "a predicate function that takes an int and returns an int." But to the C compiler, the function parentheses () have higher precedence than the pointer asterisk *.

C sees this as a function declaration that returns a pointer to an int (int*), not a pointer to a function. When you pass is_even (which returns a plain int) into that slot, you've created a type mismatch. Depending on your compiler settings, you might get a warning, or the program might try to treat the return value of is_even as a memory address, leading to a segmentation fault when the code tries to dereference it.

Grouping the Pointer with Parentheses

To fix this, we have to force the compiler to associate the * with the name of the variable, not the return type. We do this by wrapping the pointer and the name in parentheses.

// The Fix: Notice the (*predicate)
void filter_array(int *arr, int size, int (*predicate)(int)) {
    for (int i = 0; i < size; i++) {
        if (predicate(arr[i])) {
            printf("%d passed the test!\n", arr[i]);
        }
    }
}

By writing int (*predicate)(int), you're explicitly telling C: "predicate is a pointer to a function that takes an int and returns an int." It's a clunky syntax, I'll admit, but it's the only way to distinguish between a function that returns a pointer and a pointer to a function.

Using Typedefs to Keep Your Sanity

Once you start using function pointers in more than one place, the syntax becomes a nightmare. If you have five different functions taking the same predicate, you don't want to write int (*predicate)(int) every single time. It's noisy and prone to typos.

The professional way to handle this is with a typedef. You can essentially create a new type name for your function signature. I do this in almost every project that uses callbacks.

typedef int (*PredicateFunc)(int);

// Now the signature is clean and readable
void filter_array(int *arr, int size, PredicateFunc predicate) {
    // ... implementation stays the same ...
}

Now, PredicateFunc is a first-class type. If you ever need to change the predicate to take a float instead of an int, you only have to change it in one line (the typedef) rather than hunting through every function signature in your codebase.

When to Actually Use This

You might be wondering why we bother with this complexity. Function pointers are the key to implementing "strategies" or "plugins" in C. Imagine you're writing a library for a GUI. You don't know what happens when a user clicks a button—that's decided by the person using your library. You provide a way for them to "register" a function pointer, and your library calls that pointer whenever the click event occurs.

It allows your code to remain generic while the user of your code provides the specific behavior. It's the closest C gets to the "polymorphism" you see in languages like Java or C++.




📋 Practical Task

Building a Mathematical Operation Dispatch Table

Using a giant switch statement to handle different operations is common, but it's not very scalable. Your task is to replace a switch-based calculator with a Dispatch Table (an array of function pointers).

Requirements:

  • Create four functions: add, subtract, multiply, and divide. Each must take two double arguments and return a double.
  • Define a typedef for a function pointer that matches this signature.
  • Create an array of these function pointers. The order should be: add, subtract, multiply, divide.
  • Write a function calculate(int op_index, double a, double b) that uses the array to call the correct operation based on the index.
  • In main, test the calculate function by passing an index (0-3) and two numbers. Ensure you handle the case where the index is out of bounds of the array to prevent a crash.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.