In recent posts, I have shown ways of using function pointers to improve encapsulation, to make your code more maintainable, and to facilitate unit testing. I have also stressed that knowing when not to use function pointers is as important as knowing when to use them. Needless to say, the use of function pointers should be made judiciously.
When programming in an environment that discourages the use of function pointers, one may find it difficult not to violate some of the common programming principals. I will be covering one common use of function pointers which is very often better avoided, especially when writing firmware for memory constrained systems such as many types of IoT-based products.
A couple of years ago, I needed to do some maintenance on some code which used function pointers to implement a pattern borrowed from the functional programming world, mainly chosen to avoid violating the DRY (don’t repeat yourself) principle. Let’s explain this with a rough example of the pattern.
As mentioned, this pattern stems from not wanting to repeat yourself (i.e. obeying the DRY rule) when trying to perform two or more similar tasks. Generally, one avoids violating the DRY principle by breaking a function up into several smaller subparts, each one obeying the single responsibility principle. Then you call each subfunction from a separate higher-level function, making the necessary changes, and avoiding a lot of code duplication. However, in a traditional imperative language such as C, the declaration of a loop or conditional declaration cannot be abstracted to the same level at its nested contents. To put it another way, this practice cannot be used to cleanly abstract the declaration of a for loop into a separate function without also including its nested contents. However using a functional programming technique, instead, you can cleverly pass in a function pointer as a parameter containing those few lines of specialized behaviour as a parameter into the generalized function.
However, in many cases, this function passing practice has downsides:
- Stack space calculation can be a troubling concern when trying to use function pointers in memory restricted embedded products. You could easily allocate a stack that is too big, or worse, too small because your stack space calculator usually won’t work.
- Certain compilers offer ways to help calculate the correct stack size, even with function pointers. However, this requires vigilance from the coder’s perspective. For example, IAR Systems offers this workaround:
- You are to a great extent making it difficult to navigate to deeper portions of your code, making it difficult to navigate and understand (code obfuscation).
- C is generally known as an imperative programming language, so this programming pattern borrowed from the functional programming paradigm usually clashes with the programming style being used, making your code more difficult to maintain because of the inconsistency.
- In some cases, static analyzers may battle to check the integrity of your code around function pointers, allowing bugs to go undetected.
With reference to the above, I tend to discourage the use of function pointers in C programs when a reasonable alternative exists unless they are used to improves encapsulation, or unless they are used in a place you are expecting them to be used (such as when specifying a comparison method in certain types of search algorithms), and stack calculations are not an issue. Note, for example, I have recently spoken of a chaining pattern which uses function pointers in C, which improves encapsulation.
Focussing on avoiding function pointers when trying to perform a replacement of the inner portion of a for loop has several obvious alternatives. Bearing in mind that C is not an object orientated language, an obvious imperative idiom would be to do the following:
or better still, obeying the single responsibility principle, one can abstract the handling of the function pointer into its own function:
Function pointers provide powerful functionality in C programming, but they come at a price, and in many cases, this price will be too expensive to justify and their benefits won’t outweigh their costs. In many cases, you will be better off to completely eliminate them. This is especially true in embedded systems where you need to be extra vigilant about memory use.
No doubt many people may find my discouraging their use as being controversial. I have demonstrated one simple idiom for eliminating one particular case of function pointer abuse. Other idioms will certainly exist for eliminating their use in other scenarios. Feel free to share your own experiences of how you have seen them being abused, overused or simply used in bad scenarios, and resolutions that have been followed to eliminate their associated problems.
- The Power of Ten – Rules for Developing Safety Critical Code by Gerard J. Holzmann NASA/JPL Laboratory for Reliable Software Pasadena, CA 91109
- Verifying programs that use function pointers