13. Function Pointers

Way, way back (well, tutorial 5 way back), I covered pointers. Back then I said that a "pointer points to the start of the address where var is kept". In other words, to the position in memory.

A function pointer is simply a variable which contains the base address to the entry point of a function - i.e. the address in memory where a particular function is.

When you compile a program, the compiler creates an entry point for every function used. As this entry point is just an address in the code, it is possible to have a pointer variable for this. With this pointer set up, the function can be called just by using the pointer to it.

Why would you want to do this rather than call up a routine in the normal way?

Speed.

Remember the big advantage to using a pointer when transferring large amounts of data between functions - the same applies here. As you're directly calling an area of memory and supplying it with some data, the operation is far faster than a conventional call. The speed difference depends on the machine's architecture (as well as program stucture and how efficient the alternatives are).

A question which should be raised here is "if it is so fast, why don't we use function pointers every time we call a function?"

The reason is that we use function pointers to do things which are not otherwise possible (such as using bsearch(), qsort() and other similar functions) and also for self modifying programs which alter function pointers to affect their execution

The format for creating a function pointer variable is this

int (*pointer) (int var1, int var2);

There is a rule to obey here though.

The *pointer has to be the same type as the return type of the function (e.g. int, float, double etc.)

The *pointer is bracketed due to C's precedence rules.

Assigning the address of the function, just use the name without the parathenesis. A function may look like this

int numbers(int a, int b);

the assignment statement would be

pointer=numbers;

Calling the function indirectly through pointer is achieved using something like

total=(*pointer)(11,44);

or directly using

total=pointer(11,44);

A full blown example:

#include <stdio.h>

int total(int no1, int no2);

int main(void)
{
 int (*pointer) (int a, int b);
 int result;
 pointer=total;
 result=(*pointer) (11,54);
 printf("%d",result);
 return 0;
}

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

Alright, the above isn't actually of much use, but does give an idea of the potential for a function pointer.

Where the function pointer really comes into it's own is when we have a function pointer array, the beauty being that each element in the array can point to a different function - this makes the likes of multi-threading and the writing of systems software, compilers, assemblers and the likes so much simpler and faster.

The code is made efficient (and therefore fast - or at least, faster!) by this method.

The following example gives an idea of how powerful the function pointer is and that like any other array, it can be initialised.

#include <stdio.h>

int add(int a, int b);
int sub(int a, int b);
int mul(int a, int b);
int divi(int a, int b); /* divi used as Acorn C objects to div */
int (*pointer[4]) (int a, int b) = {add, sub, mul, divi};


int main(void)
{
 int res,no1,no2,operate;
 printf("Enter two numbers : ");
 scanf("%d%d",&no1,&no2);
 printf("0 : Add\n1 : Subtract\n2 : Multiply\n3 : Divide\n");

 do
 {
  printf("Enter the operation number ");
  scanf("%d",&operate);
 } while((operate<0) || (operate>3));

 res=(*pointer[operate])(no1,no2);
 printf("Result : %d",res);
 return 0;
}

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 divi(int a, int b)
{
 if (b) return a/b; /* traps if attempting to divide by zero */
 else
 return 0;
}

So, what does this program have over a program which uses a switch?

Nothing really, except it will be smaller and faster. Both the sources for this program and the switch versions are included in the downloads.

Given the power of something like a function pointer array, it would be the perfect system for a sorter (such as used in a spreadsheet or database system). In C's standard library is the function qsort() which is a fast, single dimensioned array sorter. The function prototype looks a bit hairy, but is simpler than it looks :

void qsort(void *array_to_be_sorted, size_t number, size_t size, int (*comp)(const void *a, const void *b));

size_t is defined by the C compiler, number is the number of elements in the array with size being the size of each element.

The second part of the prototype is the part we're interested in. This compares the two elements passed to the function (comp - you create this) and returns the following

condition returned result
*a < *b negative number
*a == *b zero
*a > *b positive number

The following example shows this in action

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

int compare(const void *one, const void *two)
{
 return *(int*)one-*(int*)two;
}

int main(void)
{
 int sort_me[100],loop;

 /* this is for later
 int *search_me,search_key;
 */

 for (loop=0;loop<100;loop++)
 sort_me[loop]=rand();

 qsort(sort_me,100,sizeof(int),compare);

 for(loop=0;loop<100;loop++)
 printf("%d\n",sort_me[loop]);

/* space left here for a good reason! */ 

 return 0;
}

A more useful function in the standard C library is called bsearch, this searches a sorted array given a key. The prototype is exactly the same as that for qsort except that there is an extra parameter in front specifying the search key. bsearch() returns a void* whereas qsort() returns void.

In a real-world situation, this can be a powerful tool. Say you work in a laboratory or a library, or anywhere with something that is indexed, bsearch will be very useful when trying to find anything.

If we use the qsort() example code again, but insert a request for a search key (in this case, it's just a number), the power can be seen. I have not repeated the code, what follows should replace the small remark statement.

printf("Enter a number to find : ");
scanf("%d",&search_key);

search_me=bsearch(&search_key,sort_me,100,sizeof(int),compare);
if (search_me) printf("The number is in the array\n");
else
printf("The number isn't in the array\n");

To compile this, you will need to remove the remark (/* and */) statements around the int statements

If the item is found, bsearch() returns a pointer to the item found, otherwise it returns a NULL pointer.

The comparison function can be anything you like. In an address book, you may have your entries stored in a struct array. To sort by name, you would compare the surname and if equal, the forename

typedef struct
{
 char surname[20];
 char forenames[60];
 int telephone;
} entry;

int compare_names (const void *one, const void *two);

int main(void)
{
 entry phonebook[100]; /* creates the array of "entry" structs */

 /* .... code to read in entries .... */

 qsort(phonebook, 100, sizeof(entry), compare_names);
 /* sorts the phone book by name
    then we have some code to print out the phone book, search, make the
    coffee etc */
}

int compare_names (const void *one, const void *two)
{
 int i;
 entry *a, *b;
 a = (entry*) one;
 b = (entry*) two;
 if ((i=strcmp(a->surname,b->surname))==0)
  return strcmp(a->forenames, b->forenames);
 else
 return i;
}

The nice thing is that you don't have to know how to sort, just how to compare.

This can't be all there is to a function pointer, well, no there is more. If we go back to the original example (the add, subtract etc program), we can add another function into the program without very much of a change.

As a bit of a task (it's not much of one though) add another function pointer to perform a modulus (%) operation on the numbers and a second to perform a x^y function. Remember though, both functions have to be error trapped.

Task time!

That about concludes this session. If you look back at past assignments/programs, you should have an address book and simple calculator. What you should do is re-write one or the other (or both!) so that when possible function pointers are used. Compare the compiled file size and program speed to your original versions.

Next time, I'll look at memory and command lines arguments.