Function pointers

A function has an address too, and a pointer can hold it. Calling through that pointer chooses the function at run time, which is how a library such as qsort sorts values it has never seen. The syntax is the hard part; once it is read correctly, the idea is the same as a pointer to data from lesson 27.

Declaring and calling

The declaration int (*operation)(int, int) reads inside out: the name is in parentheses with a star, so it is a pointer, and the rest says it points to a function taking two ints and returning an int. A function name decays to its address, so the initialiser needs no ampersand. Calling works with or without the explicit dereference.

#include <stdio.h> static int add(int a, int b) { return a + b; } int main(void) { int (*operation)(int, int) = add; printf("%d\n", operation(2, 3)); printf("%d\n", (*operation)(4, 5)); return 0; }

gcc -Wall -Wextra declare.c -o declare
./declare

5
9

Both calls do the same thing, and most code uses the first form. The parentheses around the star are not optional: without them int *operation(int, int) declares a function that returns a pointer to int, which is a different declaration entirely.

Callbacks

A function parameter can be a function pointer. The caller chooses the operation and the callee applies it, which turns a fixed function into a small framework. The example applies two different functions to the same value without duplicating the printing code.

#include <stdio.h> static int twice(int value) { return value * 2; } static int square(int value) { return value * value; } static void apply(int (*function)(int), int value) { printf("%d -> %d\n", value, function(value)); } int main(void) { apply(twice, 5); apply(square, 5); return 0; }

gcc -Wall -Wextra callback.c -o callback
./callback

5 -> 10
5 -> 25

The parameter type must match exactly. A callback that takes a void * can carry any data, which is the shape the standard library uses, but it also gives up the compiler's checks; this course keeps the exact type where it can.

A table of functions

An array of function pointers dispatches by index, which replaces a switch over a small set of operations. All functions must share the same signature, and a parallel array of names supplies the labels. This is the C version of a virtual table, before objects appear in lesson 40.

#include <stdio.h> static int add(int a, int b) { return a + b; } static int subtract(int a, int b) { return a - b; } static int multiply(int a, int b) { return a * b; } int main(void) { int (*operations[3])(int, int) = {add, subtract, multiply}; const char *names[3] = {"add", "subtract", "multiply"}; for (int i = 0; i < 3; i++) { printf("%-9s %d\n", names[i], operations[i](6, 3)); } return 0; }

gcc -Wall -Wextra table.c -o table
./table

add 9
subtract 3
multiply 18

The array initialiser lists the functions by name, and the index chooses one at run time. A key pressed on a menu or a bytecode opcode from a file can select the same entry, which is why interpreters are built this way.

qsort takes a comparator

qsort from stdlib.h sorts an array of unknown element type. It receives the base address, the count, the size of one element and a comparison function. The comparator gets const void * pointers, casts them back to the element type, and returns a negative number, zero or a positive number.

#include <stdio.h> #include <stdlib.h> static int compare(const void *left, const void *right) { int a = *(const int *) left; int b = *(const int *) right; return (a > b) - (a < b); } int main(void) { int values[6] = {42, 7, 19, 3, 88, 1}; qsort(values, 6, sizeof values[0], compare); for (int i = 0; i < 6; i++) { printf("%d ", values[i]); } printf("\n"); return 0; }

gcc -Wall -Wextra sort.c -o sort
./sort

1 3 7 19 42 88

The subtraction (a > b) - (a < b) avoids overflow that a plain a - b can cause, and returns exactly -1, 0 or 1. The casts from void * are the price of a callback that works for any element type.

Wrong signature, null call

A function pointer type includes the parameter list, so a pointer to int (int) cannot hold a function of two ints. GCC 15 rejects the initialisation and prints both signatures, which is enough to spot the missing parameter:

#include <stdio.h> static int add(int a, int b) { return a + b; } int main(void) { int (*operation)(int) = add; printf("%d\n", operation(3)); return 0; }

gcc -Wall -Wextra badtype.c -o badtype

badtype.c: In function 'main':
badtype.c:10:29: error: initialization of 'int (*)(int)' from incompatible pointer type 'int (*)(int, int)' [-Wincompatible-pointer-types]
10 | int (*operation)(int) = add;
| ^~~
badtype.c:3:12: note: 'add' declared here
3 | static int add(int a, int b)
| ^~~

A null function pointer compiles without complaint, because the compiler cannot know that the call will happen. The program crashes when it does, and the shell reports status 139:

#include <stdio.h> int main(void) { int (*operation)(int) = NULL; printf("%d\n", operation(3)); return 0; }

gcc -Wall -Wextra nullcall.c -o nullcall
./nullcall; echo "exit: $?"

exit: 139

The check is the same as for any pointer: compare with NULL before the call. A table of functions filled at run time should initialise every slot, or have a fallback function for the empty ones.

Exercises

  1. Add a division function to the table and make the program skip it when the second operand is zero.
  2. Write a comparator that sorts an array of strings alphabetically and sort three words with qsort.
  3. Write a function that takes a callback and an array of ints, and returns how many elements satisfy the callback.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. How is a pointer to a function taking two int values and returning an int declared?

    Answer

    int (*operation)(int, int)

    The parentheses around *operation make it a pointer, and the rest gives the function signature.

  2. What does a callback parameter let the caller choose?

    Answer

    The function that the callee applies

    The caller passes a function pointer, and the callee calls it, which turns a fixed function into a small framework.

  3. What must a qsort comparator return?

    Answer

    A negative, zero or positive value

    Only the sign matters, so the common form (a > b) - (a < b) returns -1, 0 or 1 safely.

  4. Why does a function name work as a function-pointer initialiser without &?

    Answer

    A function name decays to its address

    In an expression a function name is converted to a pointer to the function, just as an array name decays to a pointer.

  5. What does this program print? #include <stdio.h> static int add(int a, int b) { return a + b; } int main(void) { int (*operation)(int, int) = add; printf("%d\n", operation(2, 3)); return 0; }

    Answer

    5

    The pointer calls the addition function, and 2 plus 3 is 5.

  6. What does the parameter int (*function)(int) in apply accept?

    Answer

    A pointer to a function taking one int

    The parenthesised star makes the parameter a pointer to a function, and the rest gives its return type and arguments.

  7. What does this program print? #include <stdio.h> static int twice(int value) { return value * 2; } static int square(int value) { return value * value; } static void apply(int (*function)(int), int value) { printf("%d -> %d\n", value, function(value)); } int main(void) { apply(twice, 5); apply(square, 5); return 0; }

    Answer

    5 -> 10 / 5 -> 25

    twice doubles 5 and square multiplies 5 by itself, so the two lines show 10 and 25.

  8. What does this program print for the multiply entry? #include <stdio.h> static int add(int a, int b) { return a + b; } static int subtract(int a, int b) { return a - b; } static int multiply(int a, int b) { return a * b; } int main(void) { int (*operations[3])(int, int) = {add, subtract, multiply}; printf("%d\n", operations[2](6, 3)); return 0; }

    Answer

    18

    The third entry multiplies its arguments, and 6 times 3 is 18.

  9. What does this program print? #include <stdio.h> #include <stdlib.h> static int compare(const void *left, const void *right) { int a = *(const int *) left; int b = *(const int *) right; return (a > b) - (a < b); } int main(void) { int values[6] = {42, 7, 19, 3, 88, 1}; qsort(values, 6, sizeof values[0], compare); for (int i = 0; i < 6; i++) printf("%d ", values[i]); printf("\n"); return 0; }

    Answer

    1 3 7 19 42 88

    The comparator orders ascending values, so the sorted array runs from 1 to 88.

  10. This program is built with gcc -std=c17 -Wall -Wextra badtype.c -o badtype. Which error does GCC print? #include <stdio.h> static int add(int a, int b) { return a + b; } int main(void) { int (*operation)(int) = add; printf("%d\n", operation(3)); return 0; }

    Answer

    incompatible pointer type

    The function pointer signature differs from add, which is an incompatible pointer type.

The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev