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$