Pointers · about 60 min

void pointers and function pointers

Why this matters

Every pointer so far had a definite type: int *, char *, const char **. Two more kinds of pointer complete the picture, and both are about writing generic code. A void * can hold the address of any object, which is how memcpy, qsort and (next module) malloc work on data of any type. A function pointer holds the address of a function, so you can pass behavior around as a value: tell qsort how to compare, register a callback, or pick an operation from a table. These two ideas are how C does what other languages do with generics, interfaces and lambdas.

void *: an address with no type

void *p;

is a pointer that says nothing about what it points to. The rules:

So a void * is useful as a carrier: functions accept it so that callers can pass any type, and inside, the function converts it to something concrete. You have already met two such functions: printf("%p", (void *)p) expects a void *, and memset/memcpy take void * parameters, which is why you can hand them an array of any type.

Working on raw bytes

The concrete type a generic function usually converts to is unsigned char *: a pointer to individual bytes, which the C standard explicitly allows for inspecting any object's memory. Here is a swap that works for any type, given the size:

#include <stdio.h>
#include <string.h>

/* Swap two objects of any type, given their size in bytes. */
void swap_any(void *a, void *b, size_t size)
{
    unsigned char *x = a;
    unsigned char *y = b;
    for (size_t i = 0; i < size; i++) {
        unsigned char tmp = x[i];
        x[i] = y[i];
        y[i] = tmp;
    }
}

int main(void)
{
    int i1 = 1, i2 = 2;
    double d1 = 1.5, d2 = -2.25;
    char s1[8] = "left", s2[8] = "right";

    swap_any(&i1, &i2, sizeof i1);
    swap_any(&d1, &d2, sizeof d1);
    swap_any(s1, s2, sizeof s1);
    printf("%d %d | %g %g | %s %s\n", i1, i2, d1, d2, s1, s2);
    return 0;
}
2 1 | -2.25 1.5 | right left

swap_any knows nothing about ints or doubles. It sees two addresses and a byte count, and swaps the bytes. This is precisely how the library's generic functions are built, and it shows the price of genericity in C: the caller must supply the size, and nothing checks that the two pointers really point to objects of that size. Pass the wrong size and you corrupt memory.

Function pointers

Compiled functions are machine code at some address in memory, so they have addresses too. A function pointer stores one.

#include <stdio.h>

int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }

int apply(int (*op)(int, int), int x, int y)
{
    return op(x, y);
}

int main(void)
{
    int (*f)(int, int) = add;

    printf("f(3, 4) = %d\n", f(3, 4));
    f = mul;
    printf("f(3, 4) = %d\n", f(3, 4));
    printf("apply(add, 10, 5) = %d\n", apply(add, 10, 5));
    printf("apply(mul, 10, 5) = %d\n", apply(mul, 10, 5));
    return 0;
}
f(3, 4) = 7
f(3, 4) = 12
apply(add, 10, 5) = 15
apply(mul, 10, 5) = 50

Reading and writing the syntax

int (*f)(int, int);

Start from the name: f is a pointer (*f, in parentheses) to a function taking (int, int) and returning int. A recipe for writing one: take the function's prototype, int add(int, int), and replace the name with (*f). The parentheses are essential: int *f(int, int); declares a function returning int *.

   f                          machine code (read-only, executable)
  +------+                   +---------------------------+
  |  *---+------------------>| add:  lea eax,[rdi+rsi]   |
  +------+                   |       ret                 |
                             +---------------------------+
                             | mul:  mov eax,edi         |
                             |       imul eax,esi        |
                             |       ret                 |
                             +---------------------------+
  after f = mul, the arrow moves to mul

apply takes a function as a parameter. A function passed to another function so that it can be "called back" is a callback. apply does not know or care which operation it performs; the caller decides.

qsort: the callback you will use most

The C library's sort function is generic over both the element type and the ordering:

void qsort(void *base, size_t count, size_t size,
           int (*compar)(const void *, const void *));

You pass the array (as void *), the number of elements, the size of each, and a comparison function. qsort moves elements around as raw bytes (like swap_any) and calls your function whenever it needs to know the order of two elements, passing pointers to them. Your function returns negative if the first should come first, positive if second, 0 if they are equal.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int cmp_int(const void *a, const void *b)
{
    int x = *(const int *)a;
    int y = *(const int *)b;
    return (x > y) - (x < y);      /* -1, 0 or 1, and no overflow */
}

int cmp_str(const void *a, const void *b)
{
    const char *const *x = a;      /* each element is a const char * */
    const char *const *y = b;
    return strcmp(*x, *y);
}

int main(void)
{
    int nums[] = {42, -7, 19, 0, 3};
    const char *names[] = {"lisp", "c", "rust", "ada", "go"};

    qsort(nums, 5, sizeof nums[0], cmp_int);
    qsort(names, 5, sizeof names[0], cmp_str);

    for (int i = 0; i < 5; i++) printf("%d ", nums[i]);
    printf("\n");
    for (int i = 0; i < 5; i++) printf("%s ", names[i]);
    printf("\n");
    return 0;
}
-7 0 3 19 42 
ada c go lisp rust 

Two details worth studying:

Tables of function pointers

An array of function pointers turns a decision into a lookup:

int (*ops[])(int, int) = {add, mul};
printf("%d\n", ops[1](6, 7));   /* calls mul: 42 */

Read int (*ops[])(int, int) from the name: ops is an array ([]) of pointers (*) to functions (int, int) returning int. Paired with a table of names, it becomes a dispatch table: look up a command, call the matching function. Interpreters, command-line tools with subcommands (git commit, git push) and device drivers in the Linux kernel are organized this way. Adding an operation means adding a function and a table entry, not editing a growing chain of if statements. (When you learn typedef and structs in a later module, these declarations get much easier to read.)

Common mistakes

Recap

Going further: read man 3 qsort and man 3 bsearch; bsearch searches a sorted array with the same kind of comparison function and returns a void * to the match or NULL.

Exercises

Exercise 1 · quiz

Using a void pointer

int x = 5; void *p = &x; Which statements are true? Select all that apply.

Answer
  • printf("%d", *p); does not compile: a void pointer cannot be dereferenced

  • printf("%d", *(int *)p); prints 5

  • int *q = p; compiles in C without a cast

void * means "address of something of unknown type". Without a type, the compiler does not know how many bytes to read or what they mean, so *p is an error ("invalid use of void expression"). Convert it to the right pointer type first, explicitly with a cast or implicitly by assigning to an int * (C converts void * to and from any object pointer type without a cast). Arithmetic on void * is not allowed in standard C for the same reason: there is no element size to scale by. GCC accepts it as an extension and treats the size as 1, which is not portable.

Exercise 2 · quiz

Reading function pointer declarations

What is f in int (*f)(double);?

Answer
  • A pointer to a function that takes a double and returns an int

The parentheses around *f make f a pointer first; what it points to is "function (double) returning int". Without them, int *f(double); declares a function f returning int *. The pattern: write the function's prototype, then replace the name with (*name).

Exercise 3 · write code

Sort descending with qsort

Write a comparison function for qsort that sorts ints in descending order:

int cmp_desc(const void *a, const void *b)

It must return a negative number if the int at a should come before the int at b (that is, if it is larger), a positive number if after, and 0 if they are equal. Do not compute y - x: for values near INT_MIN and INT_MAX the subtraction overflows.

Then make main read up to 100 integers until end of input, sort them with qsort(values, n, sizeof values[0], cmp_desc), and print them on one line separated by single spaces. Input 3 -1 10 3 prints 10 3 3 -1.

File: main.c

Starting point

#include <stdio.h>
#include <stdlib.h>

int cmp_desc(const void *a, const void *b)
{
    /* your code here */
    return 0;
}

int main(void)
{
    int values[100];
    int n = 0;
    while (n < 100 && scanf("%d", &values[n]) == 1) {
        n++;
    }

    /* sort with qsort, then print */
    return 0;
}

Example

input

3 -1 10 3

expected output

10 3 3 -1

Example

input

1 2 3 4 5 6

expected output

6 5 4 3 2 1
Hint 1

qsort hands you pointers to two elements as const void *. Convert and read: int x = *(const int *)a;.

Hint 2

For descending order, a larger x must give a negative result. if (x > y) return -1; if (x < y) return 1; return 0;

Hint 3

A compact, overflow-free form: return (x < y) - (x > y);. Each comparison is 0 or 1.

Reference solution
#include <stdio.h>
#include <stdlib.h>

int cmp_desc(const void *a, const void *b)
{
    int x = *(const int *)a;
    int y = *(const int *)b;
    return (x < y) - (x > y);
}

int main(void)
{
    int values[100];
    int n = 0;
    while (n < 100 && scanf("%d", &values[n]) == 1) {
        n++;
    }

    qsort(values, n, sizeof values[0], cmp_desc);
    for (int i = 0; i < n; i++) {
        printf("%s%d", i > 0 ? " " : "", values[i]);
    }
    printf("\n");
    return 0;
}

Exercise 4 · write code

A calculator with a dispatch table

Write a calculator that reads lines of the form

<op> <a> <b>

until end of input, where <op> is one of add, sub, mul, max and a, b are integers. For each line print the result on its own line. For an unknown operation print unknown op: <op> instead and carry on.

Do not use an if/else chain or switch on the operation name. Write one small function per operation, all with the type int f(int, int), and put them in a table: an array of names and a parallel array of function pointers. Look the name up with a loop and call through the pointer.

For the input

add 2 3
mul 4 5
pow 2 8
max -3 -9
sub 1 10

the output is:

5
20
unknown op: pow
-3
-9

You can read each line with scanf("%15s %d %d", op, &a, &b) == 3 in the loop condition.

File: main.c

Starting point

#include <stdio.h>
#include <string.h>

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

int main(void)
{
    char op[16];
    int a, b;

    while (scanf("%15s %d %d", op, &a, &b) == 3) {
        /* look op up in a table and call the matching function */
    }
    return 0;
}

Example

input

add 2 3
mul 4 5
pow 2 8
max -3 -9
sub 1 10

expected output

5
20
unknown op: pow
-3
-9

Example

expected output

(nothing)
Hint 1

An array of function pointers: int (*funcs[])(int, int) = {add, sub, mul, max}; next to const char *names[] = {"add", "sub", "mul", "max"};.

Hint 2

Loop over the names with strcmp. When one matches, remember funcs[i] in a variable int (*f)(int, int) (start it as NULL).

Hint 3

After the loop: if f is not NULL print f(a, b), otherwise the unknown-op message.

Reference solution
#include <stdio.h>
#include <string.h>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int max(int a, int b) { return a > b ? a : b; }

int main(void)
{
    const char *names[] = {"add", "sub", "mul", "max"};
    int (*funcs[])(int, int) = {add, sub, mul, max};
    int count = sizeof names / sizeof names[0];

    char op[16];
    int a, b;

    while (scanf("%15s %d %d", op, &a, &b) == 3) {
        int (*f)(int, int) = NULL;
        for (int i = 0; i < count; i++) {
            if (strcmp(op, names[i]) == 0) {
                f = funcs[i];
                break;
            }
        }
        if (f != NULL) {
            printf("%d\n", f(a, b));
        } else {
            printf("unknown op: %s\n", op);
        }
    }
    return 0;
}