Nigel Jones Web Site Text: The example templates for "Pointers to functions" appear below. These templates may be used to build up most realistic (and some unrealistic) declarations for arrays of pointers to functions. Function pointer templates Listed below are a number of declaration examples (in increasing order of complexity) for arrays of function pointers. Before looking at the examples, a few comments concerning style / practices are called for. All of the examples are preceded with a static declaration. This is done on the assumption that the scope of a function table should be highly localized. In every example, the array pf[] is preceded with const. This declares that the array cannot be modified after its initialization. This is the normal case. However, if you really feel the need to modify the array of pointers at runtime, then go ahead and remove the const immediately preceding pf[]. There are two syntactically different ways of invoking a function via a pointer. If we have a function pointer with the declaration: void (*fnptr)(int); //fnptr is a function pointer Then it may be invoked using either of these methods: fnptr(3); //Method 1 of invoking the function (*fnptr)(3); //Method 2 of invoking the function The advantage of the first method is an uncluttered syntax. However, it makes it look as if fnptr is a function, as opposed to being a function pointer. Someone maintaining the code may end up searching in vain for the function fnptr(). With method 2, it is much clearer that we are dereferencing a pointer. However, when the declarations get complex, the added (*) can be a significant burden. Throughout the examples, both syntaxΉs are shown. In practice, the latter syntax seems to be more popular. In every example, the syntax for using a typedef is also given. It is quite permissible to use a typedef to define a complex declaration, and then use the new type like a simple type. If we stay with the example above, then an alternative declaration is: typedef void (*PFV_I )(int); PFV_I fnptr = fna; //Declare a PVFV_I typed variable and init it fnptr(3); //Call fna with 3 using method 1 (*fnptr)(3); //Call fna with 3 using method 2 The typedef declares the type PFV_I to be a pointer to a function that returns void and is passed an integer. We then simply declare fnptr to a variable of this type, and use it. Typedefs are very good when you regularly use a certain function pointer type, since it saves you having to remember and type in the declaration. The downside of using a typedef, is the fact that it is not obvious that the variable that has been declared is a pointer to a function. Thus, just as for the two invocation methods above, you can gain syntactical simplicity by hiding the underlying functionality. In the typedefs, a consistent naming convention is used. Every type starts with PF (Pointer to Function) and is then followed with the return type, followed by an underscore, the first parameter type, underscore, second parameter type and so on. For void, boolean, char, int, long, float & double, the characters V,B,C,I,L,S,D are used. (Note the use of S(ingle) for Float, to avoid confusion with F(unction)).For a pointer to a data type, the type is preceded with P. Thus PL is a pointer to a long. If a parameter is const, then a c appears in the appropriate place. Thus, cPL is a const pointer to a long, whereas a PcL is a pointer to a const long, and cPcL is a const pointer to a const long. For volatile qualifiers, v is used. For unsigned types, a u precedes the base type. For user defined data types, you are on your own! An extreme example: PFcPcI_uI_PvuC. This is a pointer to a function that returns a const pointer to a const Integer that is passed an unsigned integer and a pointer to a volatile unsigned char. Well, enough preambles, onto the examples. The first eleven examples are generic in the sense that they do not use memory space qualifiers and hence may be used on any target. Example 12 shows how to add memory space qualifiers, such that all the components of the declaration end up in the correct memory spaces. Example 1 pf[] is a static array of pointers to functions that take an INT as an argument and return void. void fna(INT); //Example prototype of a function to be called //Declaration using typedef typedef void (* const PFV_I)(INT); static PFV_I pf[] = {fna,fnb,fnc, Š fnz); //Direct declaration static void (* const pf[])(INT) = {fna, fnb, fnc, Š fnz}; //Example use INT a = 6; pf[jump_index](a); //Calling method 1 (*pf[jump_index])(a); //Calling method 2 Example 2 pf [] is a static array of pointers to functions that take a pointer to an INT as an argument and return void. void fna(INT *); //Example prototype of a function to be called //Declaration using typedef typedef void (* const PFV_PI)(INT *); static PVF_PI[] = {fna,fnb,fnc, Š fnz}; //Direct declaration static void (* const pf[])(INT *) = {fna, fnb, fnc, Š fnz}; //Example use INT a = 6; pf[jump_index](&a); //Calling method 1 (*pf[jump_index])(&a); //calling method 2 Example 3 pf [] is a static array of pointers to functions that take an INT as an argument and return a CHAR CHAR fna(INT); //Example prototype of a function to be called //Declaration using typedef typedef CHAR (* const PFC_I)(INT); static PVC_I[] = {fna,fnb,fnc, Š fnz}; //Direct declaration static CHAR (* const pf[])(INT) = {fna, fnb, fnc, Š fnz}; //Example use INT a = 6; CHAR res; res = pf[jump_index](a); //Calling method 1 res = (*pf[jump_index])(a); //Calling method 2 Example 4 pf [] is a static array of pointers to functions that take an INT as an argument and return a pointer to a CHAR. CHAR *fna(INT); //Example prototype of a function to be called //Declaration using typedef typedef CHAR * (* const PFPC_I)(INT); static PVPC_I[] = {fna,fnb,fnc, Š fnz}; //Direct declaration static CHAR * (* const pf[])(INT) = {fna, fnb, fnc, Š fnz}; //Example use INT a = 6; CHAR * res; res = pf[jump_index](a); //Calling method 1 res = (*pf[jump_index])(a); //Calling method 2 Example 5 pf [] is a static array of pointers to functions that take an INT as an argument and return a pointer to a const CHAR (i.e. the pointer may be modified, but what it points to may not). const CHAR *fna(INT); //Example prototype of a function to be called //Declaration using typedef typedef const CHAR * (* const PFPcC_I)(INT); static PVPcC_I[] = {fna,fnb,fnc, Š fnz}; //Direct declaration static const CHAR * (* const pf[])(INT) = {fna, fnb, fnc, Š fnz}; //Example use INT a = 6; const CHAR * res; res = pf[jump_index](a); //Calling method 2 res = (*pf[jump_index])(a); //Calling method 2 Example 6 pf [] is a static array of pointers to functions that take an INT as an argument and return a const pointer to a CHAR (i.e. the pointer may not be modified, but what it points to may be modified). CHAR * const fna(INT i);//Example prototype of a function to be called //Declaration using typedef typedef CHAR * const (* const PFcPC_I)(INT); static PVcPC_I[] = {fna,fnb,fnc, Š fnz}; //Direct declaration static CHAR * const (* const pf[])(INT) = {fna, fnb, fnc, Š fnz}; //Example use INT a = 6; CHAR * const res = pf[jump_index](a); //Calling method 1 CHAR * const res = (*pf[jump_index])(a); //Calling method 2 Example 7 pf [] is a static array of pointers to functions that take an INT as an argument and return a const pointer to a const CHAR (i.e. the pointer, nor what it points to may be modified) const CHAR * const fna(INT i);//Example function prototype //Declaration using typedef typedef const CHAR * const (* const PFcPcC_I)(INT); static PVcPcC_I[] = {fna,fnb,fnc, Š fnz}; //Direct declaration static const CHAR * const (* const pf[])(INT) = {fna, fnb, fnc, Š fnz}; //Example use INT a = 6; const CHAR* const res = pf[jump_index](a); //Calling method 1 const CHAR* const res = (*pf[jump_index])(a); //Calling method 2 Example 8 pf [] is a static array of pointers to functions that take a pointer to a const INT as an argument (i.e. the pointer may be modified, but what it points to may not) and return a const pointer to a const CHAR (i.e. the pointer, nor what it points to may be modified) const CHAR * const fna(const INT *i); //Example prototype //Declaration using typedef typedef const CHAR * const (* const PFcPcC_PcI)(const INT *); static PVcPcC_PcI[] = {fna,fnb,fnc, Š fnz}; //Direct declaration static const CHAR * const (* const pf[])(const INT *) = {fna, fnb, fnc, Š fnz}; //Example use const INT a = 6; const INT *aptr; aptr = &a; const CHAR* const res = pf[jump_index](aptr); //Calling method 1 const CHAR* const res = (*pf[jump_index])(aptr);//Calling method 2 Example 9 pf [] is a static array of pointers to functions that take a const pointer to an INT as an argument (i.e. the pointer may not be modified, but what it points to may ) and return a const pointer to a const CHAR (i.e. the pointer, nor what it points to may be modified) const CHAR * const fna(INT *const i); //Example prototype //Declaration using typedef typedef const CHAR * const (* const PFcPcC_cPI)(INT * const); static PVcPcC_cPI[] = {fna,fnb,fnc, Š fnz}; //Direct declaration static const CHAR * const (* const pf[])(INT * const) = {fna, fnb, fnc, Š fnz}; //Example use INT a = 6; INT *const aptr = &a; const CHAR* const res = pf[jump_index](aptr); //Method 1 const CHAR* const res = (*pf[jump_index])(aptr); //Method 2 Example 10 pf [] is a static array of pointers to functions that take a const pointer to a const INT as an argument (i.e. the pointer nor what it points to may be modified) and return a const pointer to a const CHAR (i.e. the pointer, nor what it points to may be modified) const CHAR * const fna(const INT *const i); //Example prototype //Declaration using typedef typedef const CHAR * const (* const PFcPcC_cPcI)(const INT * const); static PVcPcC_cPcI[] = {fna,fnb,fnc, Š fnz}; //Direct declaration static const CHAR * const (* const pf[])(const INT * const) = {fna, fnb, fnc, Š fnz}; //Example use const INT a = 6; const INT *const aptr = &a; const CHAR* const res = pf[jump_index](aptr); //Method 1 const CHAR* const res = (*pf[jump_index])(aptr); //Method 2 This example manages to combine five incidences of const and one of static into a single declaration. For all of its complexity, however, this is not an artificial example. You could go ahead and remove all the const and static declarations and the code would still work. It would, however, be a lot less safe, and potentially less efficient. Just to break up the monotony, here is the same declaration, but with a twist. Example 11 pf [] is a static array of pointers to functions that take a const pointer to a const INT as an argument (i.e. the pointer nor what it points to may be modified) and return a const pointer to a volatile CHAR (i.e. the pointer may not be modified, but what it points to may change unexpectedly) volatile CHAR * const fna(const INT *const i); //Example prototype //Declaration using typedef typedef volatile CHAR * const (* const PFcPvC_cPcI)(const INT * const); static PVcPvC_cPcI[] = {fna,fnb,fnc, Š fnz}; //Direct declaration static volatile CHAR * const (* const pf[])(const INT * const) = {fna, fnb, fnc, Š fnz}; //Example use const INT a = 6; const INT * const aptr = &a; volatile CHAR * const res = pf[jump_index](aptr); //Method 1 volatile CHAR * const res = (*pf[jump_index])(aptr); //Method 2 while (*res) ; //Wait for volatile register to clear With memory space qualifiers, things can get even more hairy. For most vendors, the memory space qualifier is treated syntactically as a type qualifier (such as const or volatile) and thus follows the same placement rules. For consistency, I place type qualifiers to the left of the "thing" being qualified. Where there are multiple type qualifiers, alphabetic ordering is used. Since memory space qualifiers are typically compiler extensions, they are normally preceded by an underscore, and hence come first alphabetically. Thus, a nasty declaration may look like this: _ram const volatile UCHAR status_register; To demonstrate memory space qualifier use, here is example 11 again, except this time memory space qualifiers have been added. The qualifiers are named _m1 Š _m5. Example 12 pf [] is a static array of pointers to functions that take a const pointer to a const INT as an argument (i.e. the pointer nor what it points to may be modified) and return a const pointer to a volatile CHAR (i.e. the pointer may be modified, but what it points to may change unexpectedly). Each element of the declaration lies in a different memory space. In this particular case, it is assumed that you can even declare the memory space in which parameters passed by value appear. This is extreme, but is justified on pedagogical grounds. /*An example prototype. This declaration reads as follows. Function fna is passed a const pointer in _m5 space that points to a const integer in _m4 space. It returns a const pointer in _m2 space to a volatile character in _m1 space. */ _m1 volatile CHAR * _m2 const fna(_m4 const INT * _m5 const i); /*Declaration using typedef. This declaration reads as follows. PFcPvC_cPcI is a pointer to function data type, variables based upon which lie in _m3 space. Each Function is passed a const pointer in _m5 space that points to a const integer in _m4 space. It returns a const pointer in _m2 space to a volatile character in _m1 space. */ typedef _m1 volatile CHAR * _m2 const (* _m3 const PFcPvC_cPcI)(_m4 const INT * _m5 const); static PVcPvC_cPcI[] = {fna,fnb,fnc, Š fnz}; /*Direct declaration. This declaration reads as follows. pf[] is a statically allocated constant array in _m3 space of pointers to functions. Each Function is passed a const pointer in _m5 space that points to a const integer in _m4 space. It returns a const pointer in _m2 space to a volatile character in _m1 space. */ static _m1 volatile CHAR * _m2 const (* _m3 const pf[])(_m4 const INT * _m5 const) = {fna, fnb, fnc, Š fnz}; //Declare a const variable that lies in _m4 space _m4 const INT a = 6; //Now declare a const pointer in _m5 space that points to a const //variable that is in _m4 space _m4 const INT * _m5 const aptr = &a; //Make the function call, and get back the pointer volatile CHAR * const res = pf[jump_index](&a); //Method 1 volatile CHAR * const res = (*pf[jump_index])(&a); //Method 2 while (*res) ; //Wait for volatile register to clear