Академический Документы
Профессиональный Документы
Культура Документы
NIGEL JONES
f e a t u r e
Arrays of Pointers to
Functions
In typical C code, the function jump table is not widely used. Here the author examines why jump tables aren’t
exploited more frequently and makes a case for their increased usage with real-world examples.
W
hen you examine assembly language a switch statement, so letting the compiler do the work is
code that has been crafted by an expert, preferable
you’ll usually find extensive use of func- • Function pointers are too difficult to code and maintain
tion jump tables. These jump tables,
also known as an array of pointers to The perceived danger of function pointers
functions, are used because they offer a The “function pointers are dangerous” school of thought
unique blend of compactness and execution speed, partic- comes about because code that indexes into a table and
ularly on microprocessors that support indexed addressing. then calls a function based on the index can end up just
When you examine typical C code, however, the function about anywhere. For instance, consider the following code
jump table is a much rarer beast. In this article, I will exam- fragment:
ine why jump tables aren’t used more often, and make the
case for their extensive use. I’ve also included real-world void (*pf[])(void) = {fna, fnb, fnc, ... fnz};
examples of their use.
void test(const INT jump_index)
Why aren’t function pointers popular? {
Developers usually cite three reasons for not using function /* Call the function specified by jump_index */
pointers. These reasons are: pf[jump_index]();
}
• Function pointers are dangerous
• Good optimizing compilers generate a jump table from This fragment declares pf to be an array of pointers to func-
tions that take no arguments and This approach to the use of a jump which was “better,” while a third per-
return void. The test function simply table is just as secure as an explicit mitted pragmas to let users specify
calls the specified function. As it switch statement, thereby making a what they wanted. This degree of vari-
stands, this code is dangerous for the good argument against the perceived ation doesn’t give me a warm fuzzy
following reasons: danger of function pointers. feeling.
In the case where you have, say, 26
• pf[] can be accessed by anyone Leave it to the optimizer contiguous indices, each associated
• No bounds checking is present in Many compilers will attempt to con- with a single function call (such as the
test(), such that an erroneous vert a switch statement into a jump previous example), then you can
jump_index would spell disaster table. Thus, rather than use a function almost guarantee that the compiler
pointer array, many users prefer to use will generate a jump table. But what
Consider this alternative approach, a switch statement of the form: about the case in which you have 26
which avoids these problems: non-contiguous indices that vary in
void test(const UCHAR jump_index) value from zero to 1,000? A jump table
void test(const UCHAR jump_index) { would have 974 null entries, or 1,948
{ switch jump_index “wasted” bytes on a typical microcon-
static void (*pf[])(void) = {fna, { troller. Most compilers would deem
fnb, fnc, ... fnz}; case 0: this too high a penalty to pay, and
fa(); would eschew the jump table for an if-
if (jump_index < sizeof(pf) / break; then-else-if sequence. However, if you
sizeof(*pf)) case 1: have EPROM to burn, implementing
/* Call the function fb(); this as a jump table actually costs noth-
specified by jump_index */ break; ing and buys you consistent (and fast)
pf[jump_index](); ... execution time. By coding this as a
} case 26: jump table, you ensure that the com-
fz(); piler does what you want.
The changes are subtle, but impor- break; Large switch statements present
tant. We accomplish a number of default: another problem. Once a switch state-
things with this approach: break; ment gets too far beyond a screen
} length, seeing the big picture becomes
• By declaring the array static within } harder, making the code more diffi-
the function, no one else can cult to maintain. A function pointer
access the jump table Indeed, ESP columnist Jack Crenshaw array declaration, adequately com-
• Forcing jump_index to be an advocates this approach (“Interpre- mented to explain the declaration, is
unsigned quantity means that we ters—A Recap,” September 1998, p. far more compact and allows you to
only need to perform a one-sided 23). Though I’ve never disagreed with see the overall picture. Furthermore,
test for our bounds checking Dr. Crenshaw before, there’s a first the function pointer array is potential-
• Setting jump_index to the smallest time for everything. A quick survey of ly more robust. Who hasn’t written a
data type possible that will meet the the documentation for a number of large switch statement and forgotten
requirements provides a little more compilers revealed some interesting to add a break statement on one of the
protection (most jump tables are variations. They all claimed to poten- cases?
smaller than 256 entries) tially perform conversion of a switch
• An explicit test is performed prior statement into a jump table. But the Declaration and use
to making the call, thus ensuring criteria for doing so varied consider- complexity
RUPERT ADLEY
that only valid function calls are ably. One vendor simply said their Declaration and use complexity are
made. (For performance critical compiler would attempt to perform the real reasons that jump tables
applications, the if() statement this optimization. A second claimed to aren’t used more often. In embedded
could be replaced by an assert()) use a heuristic algorithm to decide systems, where pointers normally have
Applications of function
pointers
Most textbooks cover function point-
ers in less than one page (while devot-
ing entire chapters to simple looping
constructs). The descriptions typically
say that you can take the address of a
function—and therefore define a
pointer to a function—and the syntax
looks like so-and-so. At this point, most
users are left with a complex declara-
tion and are wondering exactly what
function pointers are good for. Small
wonder, then, that function pointers
do not figure heavily in their work.
Well then, when are jump tables
useful? Arrays of function pointers are
generally useful whenever the poten-
tial exists for a variety of input into the
program that alters the program flow.
Some typical examples from the
embedded world follow.
If you really feel the need to modify the array of pointers at run time,
then go ahead and remove the constqualifier. But please don’t try to fortable with the declaration complex-
sell me any products with your code in them! ity, fear not. Visit the Embedded Systems
Programming Web site at www.embed-
ded.com/code.htm, where you’ll find a
rather palatable. Note that adding Listing 4 shows the vector table variety of declarations, ranging from
tasks and/or changing intervals simply from the industrial power supply pro- the straightforward to the downright
requires editing the timed_task[] ject I mentioned. This project was appalling. The examples are all rea-
array. No code, per se, has to be implemented using a Whitesmiths’ sonably practical in the sense that the
changed. compiler and a 68HC11. desired functionality isn’t outlandish
A couple of points are worth mak- (that is, there are no declarations for
Interrupt vectors. The fourth applica- ing about Listing 4. First, the code is arrays of pointers to functions that
tion of function jump tables is the insufficient to locate the table correct- take pointers to arrays of function
array of interrupt vectors. On most ly in memory. This would have to be pointers, and so on).
processors, the interrupt vectors are in done via linker directives.
contiguous locations, with each vector Second, note that unused inter- Declaration and use hints
representing a pointer to an interrupt rupts still have an entry in the table. All of the examples on the Web site
service routine function. Depending Doing so ensures that the table is cor- adhere to conventions I’ve found to
on the compiler, the work may be rectly aligned, and that traps can be be useful over the years.
done for you implicitly, or you may be placed on unexpected interrupts. Function pointer arrays should
forced to generate the function table. always be declared static, and the
In the latter case, implementing the If any of these examples has whet scope of a function table should be
vectors via a switch statement will not your appetite for using arrays of func- highly localized. I’ve never come
work! tion pointers, but you’re still uncom- across a situation where it made sense
to have a function table that wasn’t
declared this way.
LISTING 4 Vector table from the industrial power supply project Function pointer arrays should be
declared const, which implies that the
IMPORT VOID _stext(); /* startup routine 68HC11 specific*/ array cannot be modified after its ini-
tialization. However, if you really feel
static VOID (* const _vectab[])() = { the need to modify the array of point-
SCI_Interrupt, /* SCI */ ers at run time, then go ahead and
badSPI_Interrupt, /* SPI */ remove the const qualifier. But please
badPAI_Interrupt, /* Pulse acc input */ don’t try to sell me any products with
badPAO_Interrupt, /* Pulse acc overf */ your code in them!
badTO_Interrupt, /* Timer overf */ There are two syntactically differ-
badOC5_Interrupt, /* Output compare 5 */ ent ways of invoking a function via a
badOC4_Interrupt, /* Output compare 4 */ pointer. If we have a function pointer
badOC3_Interrupt, /* Output compare 3 */ with the declaration:
badOC2_Interrupt, /* Output compare 2 */
badOC1_Interrupt, /* Output compare 1 */ void (*fp)(int); /* fp is a func-
badIC3_Interrupt, /* Input capture 3 */ tion pointer */
badIC2_Interrupt, /* Input capture 2 */
badIC1_Interrupt, /* Input capture 1 */ Then it may be invoked using
RTI_Interrupt, /* Real time */ either of these methods:
Uart_Interrupt, /* IRQ */
PFI_Interrupt, /* XIRQ */ fp(3); /* Method 1 of
badSWI_Interrupt, /* SWI */ invoking the function */
IlOpC_Interrupt, /* illegal */ (*fp)(3); /* Method 2 of
_stext, /* cop fail */ invoking the function */
_stext, /* cop clock fail */
_stext, /* RESET */ The advantage of the first method
}; is an uncluttered syntax. However, it
makes it look as if fp is a function, as
opposed to being a function pointer.