CBE Pt. 17: Function Pointers

In short: Every function has an address. And this address can be store in an variable. Such a variable is then a pointer to a function. Of course this pointer variable should have the right type. In the following the declaration of function pointers is explained by example

No Return Value, No Parameters

This is the declaration of foo which can be used to store the address of a function that does not accept any parameters and does not return a value:

1
void (*foo)(void);

Functions with Function Parameters

And here some example where a function dummy() that gets a function pointer as parameter and simply calls this function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>

void
dummy(void (*f)(void))
{
    f();
}

// ------------------------

void
foo(void)
{
    printf("I am foo\n");
}

void
bar(void)
{
    printf("I am bar\n");
}

int
main(void)
{
    dummy(foo);
    dummy(bar);
}

Here the output

theon$ gcc -Wall -o func func.c
theon$ ./func
I am foo
I am bar
theon$ 

Since C89 the explicit usage of function pointer could be avoided and function dummy() declared as

1
void dummy(void f(void));

This is just an alternative notation but its underlying meaning and technically realization is the same. Hence, the example above can be rewritten as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>

void
dummy(void f(void))
{
    f();
}

// ------------------------

void
foo(void)
{
    printf("I am foo\n");
}

void
bar(void)
{
    printf("I am bar\n");
}

int
main(void)
{
    dummy(foo);
    dummy(bar);
}

Here the output

theon$ gcc -Wall -o func2 func2.c
theon$ ./func2
I am foo
I am bar
theon$ 

Data Structures with Function Pointers

Function pointers can be stored in data structures like arrays, lists, etc. With

1
void (*f[10])(void);

an array with 10 elements is declared. Each element is a pointer to a function. Here is became obvious to know the idea behind the notation for declaration in C:

  • In a statement the expression f[i] has the type “pointer to a function with return type void and no parameters” and

  • the expression *f[i] has the type “function with return type void and no parameters”.

In the following example an array of function pointers is used:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stddef.h>
#include <stdio.h>

void
dummy(void f(void))
{
    f();
}

// ------------------------

void
foo(void)
{
    printf("I am foo\n");
}

void
bar(void)
{
    printf("I am bar\n");
}

int
main(void)
{
    void (*f[])(void) = {
        foo,
        bar,
    };

    for (size_t i = 0; i < sizeof(f) / sizeof(f[0]); ++i) {
        printf("calling f[%zu]: ", i);
        dummy(f[i]);
    }
}

Here the output

theon$ gcc -Wall -o func3 func3.c
theon$ ./func3
calling f[0]: I am foo
calling f[1]: I am bar
theon$ 

Return Value and Parameters

With

1
int (*foo)(int, int);

a function pointer foo is declared for a function that returns a value of type int and has two parameters of type int. So for parameters simply the identifiers are omitted compared to “a normal declaration”. Hence with

1
int (*bar)(int, int (*)(int));

or equivalently

1
int (*bar)(int, int(int));

a function pointer bar is declared to a function that returns a value of type int and has two parameters. The first parameters is of type int and the second a function pointer to a function with a return value of type int and one parameter of type int. It is that simple ;-)

Below an example with function pointers to functions that have a function pointer as parameter. It compares some simple rules for numerical integration (feel free to add more sophisticated methods):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <math.h>
#include <stdio.h>

double
f(double x)
{
    return x * x;
}

double
F(double x)
{
    return x * x * x / 3;
}


// some simple rules for numerical integration

double
midpoint(double a, double b, size_t n, double f(double))
{
    double h = (b - a) / n;

    double res = 0;
    for (size_t i = 0; i < n; ++i) {
        res += f(a + (i + 0.5) * h);
    }
    return res * h;
}

double
trapezoidal(double a, double b, size_t n, double f(double))
{
    double h = (b - a) / n;

    double res = f(a) + f(b);
    for (size_t i = 1; i < n; ++i) {
        res += 2 * f(a + i * h);
    }
    return res * h / 2;
}

// apply an integration rule to a given function

void
applyRule(const char *ruleName, double a, double b, size_t n, double f(double),
          double rule(double, double, size_t, double(double)))
{
    printf("%s(%lf, %lf, %zu) = %lf\n", ruleName, a, b, n, rule(a, b, n, f));
}

// print a report for comparing numerical results

void
compare(double a, double b, size_t n, double f(double))
{
    printf("compare:\n");
    applyRule("  midpoint", a, b, n, f, midpoint);
    applyRule("  trapezoidal", a, b, n, f, trapezoidal);
}

int
main(void)
{
    printf("f(x) = x^2\n");
    compare(1, 2, 5, f);
    compare(1, 2, 10, f);
    printf("exact result: %lf\n\n", F(2) - F(1));

    printf("f(x) = sin(x)\n");
    compare(1, 2, 5, sin);
    compare(1, 2, 10, sin);
    printf("exact result: %lf\n\n", -cos(2) + cos(1));
}

Here the output

theon$ gcc -Wall -o func4 -lm func4.c
theon$ ./func4
f(x) = x^2
compare:
  midpoint(1.000000, 2.000000, 5) = 2.330000
  trapezoidal(1.000000, 2.000000, 5) = 2.340000
compare:
  midpoint(1.000000, 2.000000, 10) = 2.332500
  trapezoidal(1.000000, 2.000000, 10) = 2.335000
exact result: 2.333333

f(x) = sin(x)
compare:
  midpoint(1.000000, 2.000000, 5) = 0.958045
  trapezoidal(1.000000, 2.000000, 5) = 0.953259
compare:
  midpoint(1.000000, 2.000000, 10) = 0.956848
  trapezoidal(1.000000, 2.000000, 10) = 0.955652
exact result: 0.956449
theon$