CSCI 2021 Function Pointers Tutorial
CODE DISTRIBUTION: function-pointers-code.zip
CHANGELOG: Empty
1 Rationale
C programmers constantly make use of pointers to various kinds of data. It less common to make use of Function Pointers, variables that refer to a function but can be redirected to point at a different function instead. However, certain patterns of problem can be addressed elegantly through use of function pointers. This brief tutorial and accompanying code is designed to give an overview of using function pointers and illustrate a few spots where they are used in the C standard library.
Grading
This is an optional tutorial. There is no grade associated with it and may be skipped if it is not of interest to you at this time.
Codepack
File | Notes |
---|---|
Makefile |
Builds all programs |
fp_basics.c |
Simple example of a function pointer |
qsort_demo.c |
Demonstrates several function pointers used with the qsort() library function |
sort_floats.c |
Demonstrates a table of comparison functions used with qsort() |
sort_doubles.c |
Additional code examples |
2 Function Pointer Basics
While functions themselves are a stream of bytes that represent
instructions to perform, the value associated with function names in C
is usually the starting address of the function's instructions.
This is demonstrated at the top of main()
in the fp_basics.c
where
the following line appears:
printf("main: %p\n", main); // print the 'value' associated with main, its address
which will render the following output when the program is compiled and run
> ./fp_basics main: 0x563402b0415a ...
This representation of the "whole" function as its starting address is similar to the approach that C takes with arrays where the bare name associated with arrays is its starting address, a pointer value.
Since the starting address of functions is the same size (32-bits or
64-bits) as other pointer kinds, C supports Function Pointers as a
type of variable albeit with somewhat obtuse syntax. This appears a
few lines down in fp_basics.c
in main()
.
int (*func_ptr)(int) = NULL; // initialize function pointer to NULL //| | | +-> 1 arg, an int //| | +->name of pointer variable //| +-> is a pointer to a function //+-> return type of function pointed to
A new variable func_ptr
is introduced which initially has value
NULL
. According to the type of the variable
- It is a pointer to a function
- The function pointed at takes 1 argument, an integer
- The function pointed at returns an integer
This type specification is required so that at the location where the
function is invoked through the pointer, the compiler can still
generate the correct assembly code to call
the function
- The compiler can pass arguments in the correct locations (register
edi
in x86-64) - The compiler will know where to find the return value (register
eax
in x86-64)
The initial value for the function pointer is NULL but this can be changed by assigning it to an appropriate function.
func_ptr = increment; // point instead to increment() printf("increment: %p\n", increment); printf("func_ptr: %p\n", func_ptr);
which gives the following output when run.
increment: 0x563402b04139 func_ptr: 0x563402b04139
Finally, calling the function through the pointer uses the same syntax as standard function invocation:
int reti = func_ptr(3); // call function via pointer printf("func_ptr(3) = %d\n", reti);
giving output
func_ptr(3) = 4
Unlike standard functions, function pointers can be re-assigned. The
results that they produce when called are then associated with the new
function to which they point. This is demonstrated in the latter half
of fp_basics.c
.
func_ptr = triple; // change function pointer to triple() printf("triple: %p\n", triple); printf("func_ptr: %p\n", func_ptr); int rett = func_ptr(3); // call function via pointer printf("func_ptr(3) = %d\n", rett);
which gives the following output when run.
triple: 0x563402b04148 func_ptr: 0x563402b04148 func_ptr(3) = 9
3 qsort()
Library Function
The C standard library provides a general purpose implementation of the Quick Sort algorithm. According the manual page, this function has a somewhat intimidating prototype:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
The many void*
types present are to allow qsort()
to be called with
any type of data. A standard call is much easier to understand and
tends to look something like this:
qsort(array, array_len, element_size, compare_func); // array: address of array to sort // array_len: number elements in the array (length) // element_size: size in bytes of elements in the array // compare_func: pointer to a function that will compare 2 elements
The 4th argument appears odd in the prototype for qsort()
because it
is a function pointer. compare_func(a,b)
is intended to compare to
elements in the array and indicate their sorting order. It should
return one of the following kinds of integers
compare_func(a,b) < 0
: Negative return indicatea
sorts beforeb
compare_func(a,b) = 0
: Zero return indicatesa
andb
are equal, their sorting order doesn't mattercompare_func(a,b) > 0
: Zero return indicatesa
sorts afterb
This is a common idea that sees use in many programming environments including Java's Comparable and Comparator interfaces, OCaml array sorting, and most other sensible programming environments excluding Python which inexplicably decided to make this useful functionality hard to access in modern versions.
qsort_demo.c
demonstrates use of qsort()
on several types of
data. The code starts with several simple comparison functions.
// compare two ints pointed to int intcmp(const int *xp, const int *yp) { return *xp - *yp; } // compare two ints pointed to, reverse order int rev_intcmp(const int *xp, const int *yp) { return *yp - *xp; }
These functions take int*
as arguments which is mildly problematic
due to qsort()
requiring the comparison to take void*
arguments. This is repaired by creating a "convenience" type to allow
casting of the integer comparison functions.
// convenience type to enable casting of typed comparisong functions // to the void-ish functions expected by qsort() typedef int (*cmp_t) (const void *, const void *);
Once established, one can call qsort()
with the different comparison
functions to sort in differing orders.
int int_arr[10] = {1, 6, 5, 2, 3, 9, 4, 7, 8, 0}; // array of scrambled integers qsort((void*) int_arr, 10, sizeof(int), (cmp_t) intcmp); // sort in ascending order ... // ||||| ... // caste ... // ||||| qsort((void*) int_arr, 10, sizeof(int), (cmp_t) rev_intcmp); // sort in descending order
Some standard C functions follow the comparison function convention
required by qsort()
to make them compatible. strcmp(()
is one such
function meaning it can be used with qsort()
to sort strings.
char *str_arr[5] = {"banana", "orange", "peach", "apple", "grape"}; qsort((void*) str_arr, 5, sizeof(char *), (cmp_t) strcmp);
4 Tables of Function Pointers
Some situations arise in which the same body of code is meant to be run many times but with different functions called in the midst of it. Function pointers can help to solve this in an ideal way as they allow a true "variable" for the function call.
The file sort_floats.c
demonstrates this idea. The main body of the
the code wants to show how an array of floating point values is sorted
according to different comparison functions. Several of these are
appear at the top of the code.
int intcmp(int *xp, int *yp){ // compare two integers return -(*xp < *yp) + (*xp > *yp); // a bit of trickiness to do int comparisons and generate -1,0,+1 } int intcmp_rev(int *xp, int *yp){ // reverse integer compare return -(-(*xp < *yp) + (*xp > *yp)); } int floatcmp(float *xp, float *yp){ // compare two floating point values return -(*xp < *yp) + (*xp > *yp); // a bit of trickiness to do int comparisons and generate -1,0,+1 } int floatcmp_rev(float *xp, float *yp){ // reverse float compare return -(-(*xp < *yp) + (*xp > *yp)); }
To facilitate easy iteration over these functions, a "table" is created of them, an array of structs where the name and address of the functions are stored.
typedef struct { // type to hold combo of string name and function pointer char *name; // name of the comparison to use cmp_t func; // function to use, takes (void*) args, returns an int } compare_func_t; compare_func_t cfuncs[] = { // table of comparison functions {.name="intcmp" , .func=(cmp_t) intcmp }, // castes required to coerce {.name="intcmp_rev" , .func=(cmp_t) intcmp_rev }, // different types of functions {.name="floatcmp" , .func=(cmp_t) floatcmp }, // to unifying type of struct {.name="floatcmp_rev" , .func=(cmp_t) floatcmp_rev}, // field {.name=NULL} // 'null' terminate the table };
Each instance of a compare_func_t
has a cf.name
string field and
cf.func
comparison function field. Note the use of casting which
forces both the integer and floating point comparison functions to
match the void*
type required for this struct field.
Finally, the main()
function goes through a loop using each of
the functions in the table as an argument to qsort()
.
for(int i=0; cfuncs[i].name != NULL; i++){ // iterate over table of comparison functions ... printf("sorting using '%s'\n", cfuncs[i].name); // print name of function qsort((void*) arr, 10, sizeof(float), // same 3 args each iter to qsort() cfuncs[i].func); // last arg varies: use a different compare func ... }
This results in sorting in different orders via qsort()
according
to the function pointer passed in.
5 Additional Info
Like qsort()
, the C standard library bsearch()
provides standard
implementation of binary search. It also makes use of comparison
function pointers to handle arbitrary searching within a sorted array
of data.
void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));