student@ubuntu:~$
advanced-c Lesson 13 10 min read

Function Pointers

Passing behavior as data — C's version of callbacks and lambdas

Reading: Supplemental (not in textbook)

Quick check before you start: In Java, you can pass a Comparator to Collections.sort(). That is a function pointer in disguise. Read on to see how C does it without objects.

Practice this topic: Function Pointers skill drill

After this lesson, you will be able to:

  • Declare a function pointer variable
  • Use typedef to create readable function pointer aliases
  • Write a comparator callback for sorting
  • Use qsort from the C standard library

Declaration Syntax

A function pointer stores the address of a function. The syntax is notoriously ugly:

int (*compare)(int, int);

Read it from the inside out: compare is a pointer to a function that takes two int parameters and returns an int.

The parentheses around *compare are critical. Without them, int *compare(int, int) declares a function that returns int* — a completely different thing.

Assigning and Calling

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

int (*op)(int, int);

op = add;
printf("%d\n", op(3, 4));  // prints 7

op = sub;
printf("%d\n", op(3, 4));  // prints -1

The function name without parentheses gives you its address. Calling through the pointer looks identical to calling the function directly.

typedef Makes It Readable

The raw syntax is hard to read and easy to get wrong. Use typedef:

typedef int (*Comparator)(const void*, const void*);

Now Comparator is a type you can use for variables and parameters:

void sort(int *arr, int n, Comparator cmp);

Much cleaner than embedding the full pointer syntax in every parameter list.

Writing a Comparator

A comparator takes two elements and returns:

  • Negative if the first should come before the second
  • Zero if they are equal
  • Positive if the first should come after the second
int compare_ints(const void *a, const void *b) {
    int x = *(const int*)a;
    int y = *(const int*)b;
    return x - y;  // ascending order
}

The const void* parameters are required by qsort. You cast them to the actual type inside the function.

Using qsort

qsort is the C standard library’s sorting function. It sorts any array if you give it a comparator:

#include <stdlib.h>

int arr[] = {5, 2, 8, 1, 9};
int n = sizeof(arr) / sizeof(arr[0]);

qsort(arr, n, sizeof(int), compare_ints);
// arr is now {1, 2, 5, 8, 9}

The four arguments:

  1. Pointer to the array
  2. Number of elements
  3. Size of each element
  4. Comparator function

qsort does not know what type your array holds. It uses the size to move bytes around and the comparator to decide ordering. This is generic programming in C.

Reverse Order

To sort descending, flip the comparison:

int compare_ints_desc(const void *a, const void *b) {
    int x = *(const int*)a;
    int y = *(const int*)b;
    return y - x;  // descending order
}

Or negate the result of the ascending comparator. Same data, different behavior — controlled by the function pointer you pass.

Sorting Structs

qsort works on any array, including arrays of structs. Here is a comparator that sorts students by GPA in ascending order:

typedef struct {
    char name[50];
    double gpa;
} Student;

int compare_by_gpa(const void *a, const void *b) {
    const Student *s1 = (const Student*)a;
    const Student *s2 = (const Student*)b;

    if (s1->gpa < s2->gpa) return -1;
    if (s1->gpa > s2->gpa) return 1;
    return 0;
}

The comparator casts the void* pointers to Student*, then compares the gpa fields. Do not subtract doubles — floating-point subtraction truncates to int and produces wrong results for small differences. Use the explicit if/else pattern instead.

Student roster[] = {{"Alice", 3.7}, {"Bob", 2.9}, {"Carol", 3.4}};
qsort(roster, 3, sizeof(Student), compare_by_gpa);
// roster is now: Bob (2.9), Carol (3.4), Alice (3.7)

To sort by a string field like name, use strcmp inside the comparator:

int compare_by_name(const void *a, const void *b) {
    const Student *s1 = (const Student*)a;
    const Student *s2 = (const Student*)b;
    return strcmp(s1->name, s2->name);
}

strcmp already returns negative, zero, or positive, so you can return its result directly.


Check Your Understanding
Which declaration correctly creates a function pointer named fp that points to a function taking two ints and returning an int?
Aint *fp(int, int);
Bint (*fp)(int, int);
Cint (fp*)(int, int);
D(*int)(fp)(int, int);
Answer: B. The parentheses around *fp are what make it a pointer to a function rather than a function returning a pointer. Option A declares a function named fp that returns int*.

What Comes Next

Function pointers let you parameterize behavior. But qsort uses void* — a typeless pointer. Next, you will learn how void pointers work and why they are C’s mechanism for generic programming.

Next: Void Pointers & Generics