The purpose of this handout is to explain the underlying concepts and language syntax related to pointers to functions. There isn't any material here to explain why you might want to use pointers to functions. Lab[7] has two programming exercises designed to help show how pointers to functions can be useful.
However, you might not be aware that every function has an address as well. This simple concept is essential in understanding how function calls and returns work in machine language. If you have experience working with assembly language, you may understand the idea already; if not, you will learn about it next year in ENEL 415.
When a program is running, machine code translations of all of its functions are stored in memory. Machine code consist of a sequence of instructions. An instruction is a bit pattern that tells the central processing unit (CPU) of a computer to perform a single, relatively simple action, such as copying an int from a memory location into a register within the CPU, or adding a value in one CPU register to the value in another CPU register.
A key question is, ``When the CPU finishes executing an instruction, how does it know which instruction to execute next?'' For now, you can assume that this happens by magic, but you will see in ENEL 415 the mechanisms for finding the next instruction are quite straightforward.
The address of a function is the location in computer memory where the first machine code instruction of the function is stored. Given the address, argument types, and return type of a function, it's possible to call a function using its address rather than its name. It is this fact about computer organization that allows the use of pointers to functions in languages like C and C++.
A typical process (a process is a running program) on a typical Unix system has its memory organized something like this:
The region of memory labeled ``machine code for functions'' is traditionally known as the text segment; this name is confusing because in this context the word text means ``machine code'', not ``characters encoded in a character set such as ASCII''.![]()
For programs running on other systems, such as MS-DOS, some of the details may differ, but the general ideas are the same.
Now consider a simple example C program:
#include <stdio.h>
int cube(int);
int main(void)
{
int x = 4, y;
y = cube(x);
printf("The cube of %d is %d.\n", x, y);
return 0;
}
int cube(int arg)
{
return arg * arg * arg;
}
By playing around with gdb I was able to get the information needed to draw this partial memory map of the program's machine code when the program was compiled and linked on a Sun workstation:
Note that the memory used to store a function's machine code is NOT the same as the memory used for that function's activation record(s).![]()
To an applications programmer, the most relevant point about this example is that a function like cube really does have a well-defined address. But before moving on to pointers to functions, there are a couple of other interesting points to be made:
void start(void)
{
int main_result;
initialize_a_lot_of_things();
main_result = main();
exit(main_result);
}
#include <stdio.h>
void foo(int arg);
void bar(int arg);
typedef void FuncType(int);
int main(void)
{
FuncType *func_ptr;
func_ptr = &foo;
(*func_ptr)(17);
func_ptr = &bar;
(*func_ptr)(42);
return 0;
}
void foo(arg) { printf("foo got an arg of %d\n", arg); }
void bar(arg) { printf("bar got an arg of %d\n", arg); }
The output is
foo got an arg of 17 bar got an arg of 42How does the program work? The first thing is to understand the declarations of FuncType and func_ptr. The typedef declaration makes FuncType an alias for the type ``function with one int argument and no return value''. That means that the type of func_ptr is ``pointer to function with one int argument and no return value''.
Next, you have to know that
func_ptr = &foo;
means ``put the address of foo into func_ptr''.
Finally,
(*func_ptr)(17);
means ``call the function pointed to by func_ptr
with an argument of 17''.
So the two lines
func_ptr = &foo;
(*func_ptr)(17);
are really just a rather bizarre way of writing
foo(17);
You'll see some much more practical uses of pointers to functions
in Lab[7].
The C/C++ operator precedence rules force you to put parentheses around *func_ptr in the function call. If you wrote
*func_ptr(17); /* INCORRECT CODE */
the compiler would apply operators as if you had written
*(func_ptr(17)); /* EQUIVALENT OF ABOVE INCORRECT CODE */
which is not what you want.
typedef void TypeOne (int, int); typedef double TypeTwo (int, int); typedef double TypeThree (double); typedef void TypeFour (int, int);The types TypeOne, TypeTwo and TypeThree are distinct function types, because of differences in argument types or return types. So TypeOne*, TypeTwo* and TypeThree* are distinct pointer types.
On the other hand, TypeOne and TypeFour are two aliases for the same function type (function with two int arguments and no return value). So TypeOne* and TypeFour* are the same pointer type.
It's possible to declare a pointer to a function without first setting up a typedef for the function type. Here is an example of code that does this:
void (*signal(int sig, void (*func)(int)))(int);The above line is actually a prototype for signal, which is a function belonging to the standard C library. It can be made considerably less intimidating by means of a typedef:
typedef void SignalHandler(int); SignalHandler *signal(int sig, SignalHandler *func);Now it's easy to see that signal is a function that takes an int and a pointer to a SignalHandler as arguments, and returns a pointer to a SignalHandler. When you write code involving pointers to functions, use typedef to keep the code readable.
In code that uses pointers to functions, the operators & and * are sometimes optional - you can leave them out without changing the meaning of code. This is a bit bizarre; after all, given the declarations
int i;
int *p;
the expressions i and &i have quite different meanings,
as do p and *p.
The rules for pointers to functions are different.
Consider this code, taken from the example program:
func_ptr = &foo;
(*func_ptr)(17);
You could also write it this way:
func_ptr = foo;
(*func_ptr)(17);
Or you could write it this way:
func_ptr = &foo;
func_ptr(17);
Yet another option is:
func_ptr = foo;
func_ptr(17);
My advice on this issue is: