designate-427537_640In 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.

//functionalProgramming.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define N_ELEMENTS(array) (sizeof(array)/sizeof(*array))

typedef uint16_t (*Formula_type)(uint16_t last, uint8_t nextItem);

static uint16_t add(uint16_t last, uint8_t nextItem)
{
   return (last + nextItem);
}

static uint16_t mul(uint16_t last, uint8_t nextItem)
{
   return (last * nextItem);
}

static uint16_t apply_formula(uint8_t list[], uint8_t numItems, Formula_type chosen_formula)
{
   uint16_t total = 0;
   if (0 != numItems)
   {
      total = list[0];
      for (int8_t ctrNum = 1u; ctrNum < numItems; ++ctrNum)
      {
         total = chosen_formula(total, list[ctrNum]);
      }
   }
   return (total);
}

int main(void)
{
   uint8_t items[] =   { 1, 2, 3, 4 };
   uint16_t total_add = apply_formula(items, N_ELEMENTS(items), add);
   uint16_t total_mul = apply_formula(items, N_ELEMENTS(items), mul);
   printf("Total from adding      = %d\n"
          "Total from multiplying = %d\n", total_add, total_mul);

   return EXIT_SUCCESS;
}

 

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.

pexels-photo-63437

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.

no-39415_640

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:

//withoutPointerCalling.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define N_ELEMENTS(array) (sizeof(array)/sizeof(*array))

typedef uint16_t (*Formula_type)(uint16_t last, uint8_t nextItem);

static uint16_t add(uint16_t last, uint8_t nextItem)
{
   return (last + nextItem);
}

static uint16_t mul(uint16_t last, uint8_t nextItem)
{
   return (last * nextItem);
}

static uint16_t apply_formula(uint8_t list[], uint8_t numItems, Formula_type chosen_formula)
{
   uint16_t total = 0;
   if (0 != numItems)
   {
      total = list[0];
      for (int8_t ctrNum = 1u; ctrNum < numItems; ++ctrNum)
      {
         if (add == chosen_formula)
         {
            total = add(total, list[ctrNum]);
         }
         else if (mul == chosen_formula)
         {
            total = mul(total, list[ctrNum]);
         }
         else
         {
            puts("Unhandled Function");
            exit(EXIT_FAILURE); //unhandled
         }
      }
   }
   return (total);
}


int main(void)
{
   uint8_t items[] =   { 1, 2, 3, 4 };
   uint16_t total_add = apply_formula(items, N_ELEMENTS(items), add);
   uint16_t total_mul = apply_formula(items, N_ELEMENTS(items), mul);
   printf("Total from adding      = %d\n"
          "Total from multiplying = %d\n", total_add, total_mul);

   return EXIT_SUCCESS;
}

 

or better still, obeying the single responsibility principle, one can abstract the handling of the function pointer into its own function:

//cleanerWithoutPointerCalling.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define N_ELEMENTS(array) (sizeof(array)/sizeof(*array))

typedef uint16_t (*Formula_type)(uint16_t last, uint8_t nextItem);

static uint16_t add(uint16_t last, uint8_t nextItem)
{
   return (last + nextItem);
}

static uint16_t mul(uint16_t last, uint8_t nextItem)
{
   return (last * nextItem);
}

static uint16_t singleCalc(uint16_t total, uint8_t ToAggregate, Formula_type chosen_formula)
{
   if (add == chosen_formula)
   {
      total = add(total, ToAggregate);
   }
   else if (mul == chosen_formula)
   {
      total = mul(total, ToAggregate);
   }
   else
   {
      puts("Unhandled Function");
      exit(EXIT_FAILURE); //unhandled
   }

   return total;
}

static uint16_t apply_formula(uint8_t list[], uint8_t numItems, Formula_type chosen_formula)
{
   uint16_t total = 0;
   if (0 != numItems)
   {
      total = list[0];
      for (int8_t ctrNum = 1u; ctrNum < numItems; ++ctrNum)
      {
         total = singleCalc(total, list[ctrNum], chosen_formula);
      }
   }
   return (total);
}

int main(void)
{
   uint8_t items[] =   { 1, 2, 3, 4 };
   uint16_t total_add = apply_formula(items, N_ELEMENTS(items), add);
   uint16_t total_mul = apply_formula(items, N_ELEMENTS(items), mul);
   printf("Total from adding      = %d\n"
          "Total from multiplying = %d\n", total_add, total_mul);

   return EXIT_SUCCESS;
}

 

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.

Supporting Articles


5 Comments

Gilson Fonseca · April 18, 2016 at 5:21 pm

It’s safe and easy to follow what you did. I suggest a third approach to mix those improvements: test on apply_formula() if the used formula is valid before entering for loop and keep using the pointer itself for speed up over interactions.

    Firmware Programming by Bryan Jarmain · April 18, 2016 at 6:01 pm

    Thanks for the suggestion, @Gilson. Yes, that could help if after profiling your code, you become aware that the extra if statement is a bottleneck to performance, and you need to squeeze out some extra performance. Of course, you could also repeat the entire for loop with each respective calculations nested and embedded, the loop selected being chosen based on “chosen_formula”. More code space, but faster still – there is always some tradeoff between maintainability, size, and speed. 🙂

Peter · January 3, 2017 at 11:11 am

I still don’t understand why you still are comparing with the chosen_formula function pointer? An enum would make the code so much clearer if there is no need for function pointers.

enum {ADD=0, MUL} operation;

static uint16_t singleCalc(uint16_t total, uint8_t ToAggregate, operation op)
{
switch(op) {
case ADD:
total = add(total, ToAggregate);
break;

case MUL:
total = add(total, ToAggregate);
break;

default:
puts(“Unhandled Function”);
exit(EXIT_FAILURE); //unhandled
}
return total;
}

Besides, a switch-case can be more efficient since a good compiler can turn it into a jump-table compared to an if-else. If the compiler also can optimize enums to be less than 32 bits in size there would also be less space used on the stack for each function call where the enum is used as an argument compared to function pointers.

Last but not least, it is better to not have the function pointer at all since it can trick the API user that the code is more flexible when it is not. You actually have to read the ‘singleCalc’ function to actually understand that only ‘add’ and ‘mul’ are the operations that are supported.

Firmware Programming by Bryan Jarmain · January 3, 2017 at 4:06 pm

Thanks for your suggestion Peter. Agreed that a switch statement is a valid alternative, and it is something I debated with myself. I chose to rather directly use function pointers (but not jump to them directly) since I felt it useful to retain the ability to jump directly to the chosen calculation function when navigating within an editor (without navigating to the singleCalc function first).

So, at the time I felt it wound be better for maintainability. That said, I am sure one could devise a further improved solution (using macros) to build on both your suggestion and my solution and achieve the desired efficiency, the navigability, and also gain static checking (where the compiler could warn you if any case statements are missed).

Perhaps a subject to explore in another article…

Avoid the abuse of Function Pointers in C – Firmware Programming · July 14, 2018 at 5:53 pm

[…] This article has moved. Please visit it by clicking here. […]

Leave a Reply

Your email address will not be published. Required fields are marked *

%d bloggers like this: