|
Branch Tables via Function Pointer Arrays in C |
|
|
|
|
Written by Hemanshu Patel
|
|
Saturday, 24 November 2007 |
|
Page 5 of 6
Interrupt vector tables The fourth application of function jump tables is the array of interrupt vectors. On most processors, the interrupt vectors are in contiguous locations, with each vector representing a pointer to an interrupt service routine function. Depending upon the compiler, the work may be done for you implicitly, or you may be forced to generate the function table. In the latter case, implementing the vectors via a switch statement will not work!
Here is the vector table from the industrial power supply project mentioned above. This project was implemented using a Whitesmiths' compiler and a 68HC11 microncontroller.
IMPORT VOID _stext(); /* startup routine -68HC11 specific*/
static VOID (* const _vectab[])() = { SCI_Interrupt , /* SCI */ badSPI_Interrupt, /* SPI */ badPAI_Interrupt, /* Pulse acc input */ badPAO_Interrupt, /* Pulse acc overf */ badTO_Interrupt, /* Timer overf */ badOC5_Interrupt, /* Output compare 5 */ badOC4_Interrupt, /* Output compare 4 */ badOC3_Interrupt, /* Output compare 3 */ badOC2_Interrupt, /* Output compare 2 */ badOC1_Interrupt, /* Output compare 1 */ badIC3_Interrupt, /* Input capture 3 */ badIC2_Interrupt, /* Input capture 2 */ badIC1_Interrupt, /* Input capture 1 */ RTI_Interrupt, /* Real time */ Uart_Interrupt, /* IRQ */ PFI_Interrupt, /* XIRQ */ badSWI_Interrupt, /* SWI */ IlOpC_Interrupt, /* illegal */ _stext, /* cop fail */ _stext, /* cop clock fail */ _stext, /* RESET */ };
A couple of points are worth making:
1. The above is insufficient to locate the table correctly in memory. This has to be done via linker directives. 2. Note that unused interrupts still have an entry in the table. Doing so ensures that: * The table is correctly aligned * Traps can be placed on unexpected interrupts
If any of these examples has whet your appetite for using arrays of function pointers, but you are still uncomfortable with the declaration complexity, then fear not! You will find a variety of declarations, ranging from the straightforward to the downright appalling below. The examples are all reasonably practical in the sense that the desired functionality is not outlandish (that is, there are no declarations for arrays of pointers to functions that take pointers to arrays of function pointers and so on). Declaration and use hints
All of the examples below adhere to conventions that I have found to be useful over the years, specifically:
1. All of the examples are preceded by static . This is done on the assumption that the scope of a function table should be highly localized, ideally within an enclosing function. 2. In every example the array pf[] is also preceded with const. This declares that the pointers in the array cannot be modified after initialization. This is the normal (and safe) usage scenario. 3. 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, each syntax is shown. In practice, the latter syntax seems to be more popular--and you should use only one. 4. 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 parameter 3 using method 1 (*fnptr)(3); // Call fna with parameter 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 and 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.
Function pointer templates 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 1pf[] 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 2pf [] 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 3pf [] 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 4pf [] 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 5pf [] 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 6pf [] 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 7pf [] 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 8pf [] 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 9pf [] 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 10pf [] 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.
|
|
| |
|
|