Search code examples
cmicrocontroller

Why do we use structure of function pointers?


As they say, your learn coding techniques from others' code. I've been trying to understand couple of free stacks and they all have one thing in common: Structure of function pointers. I've following of questions related to this architecture.

  1. Is there any specific reason behind such an architecture?
  2. Does function call via function pointer help in any optimization?

Example:

void do_Command1(void)
{
  // Do something
}

void do_Command2(void)
{
  // Do something
}

Option 1: Direct execution of above functions

void do_Func(void)
{
  do_Command1();
  do_Command2();
}

Option 2: Indirect execution of above functions via function pointers

// Create structure for function pointers
typedef struct 
{
 void (*pDo_Command1)(void);
 void (*pDo_Command2)(void);
}EXECUTE_FUNC_STRUCT;

// Update structure instance with functions address    
EXECUTE_FUNC_STRUCT ExecFunc = {
 do_Command1,
 do_Command2,
};


void do_Func(void)
{
  EXECUTE_FUNC_STRUCT *pExecFunc; // Create structure pointer
  
  pExecFun = &ExecFunc; // Assign structure instance address to the structure pointer
    
  pExecFun->pDo_Command1(); // Execute command 1 function via structure pointer
  pExecFun->pDo_Command2(); // Execute command 2 function via structure pointer
}

While Option 1 is easy to understand and implement, why do we need to use Option 2?


Solution

  • While Option 1 is easy to understand and implement, why do we need to use Option 2?

    Option 1 doesn't allow you to change the behavior without changing the code - it will always execute the same functions in the same order every time the program is executed. Which, sometimes, is the right answer.

    Option 2 gives you the flexibility to execute different functions, or to execute do_Command2 before do_Command1, based decisions at runtime (say after reading a configuration file, or based on the result of another operation, etc.).

    Real-world example from personal experience - I was working on an application that would read data files generated from Labview-driven instruments and load them into a database. There were four different instruments, and for each instrument there were two types of files, one for calibration and the other containing actual data. The file naming convention was such that I could select the parsing routine based on the file name. Now, I could have written my code such that:

    void parse ( const char *fileName )
    {
      if ( fileTypeIs( fileName, "GRA" ) && fileExtIs( fileName, "DAT" ) )
        parseGraDat( fileName );
      else if ( fileTypeIs( fileName, "GRA" ) && fileExtIs ( fileName, "CAL" ) )
        parseGraCal( fileName );
      else if ( fileTypeIs( fileName, "SON" ) && fileExtIs ( fileName, "DAT" ) )
        parseSonDat( fileName );
      // etc.
    }
    

    and that would have worked just fine. However, at the time, there was a possibility that new instruments would be added later and that there may be additional file types for the instruments. So, I decided that instead of a long if-else chain, I would use a lookup table. That way, if I did have to add new parsing routines, all I had to do was write the new routine and add an entry for it to the lookup table - I didn't have to modify any of the main program logic. The table looked something like this:

    struct lut {
      const char *type;
      const char *ext;
      void (*parseFunc)( const char * );
    } LUT[] = { {"GRA", "DAT", parseGraDat },
          {"GRA", "CAL", parseGraCal },
          {"SON", "DAT", parseSonDat },
          {"SON", "CAL", parseSonCal },
          // etc.
        };
    

    Then I had a function that would take the file name, search the lookup table, and return the appropriate parsing function (or NULL if the filename wasn't recognized):

    void (*parse)(const char *) = findParseFunc( LUT, fileName );
    if ( parse )
      parse( fileName );
    else
      log( ERROR, "No parsing function for %s", fileName );
    

    Again, there's no reason I couldn't have used the if-else chain, and in retrospect it's probably what I should have done for that particular app1. But it's a really powerful technique for writing code that needs to be flexible and responsive.


    1. I suffer from a tendency towards premature generalization - I'm writing code to solve what I think will be issues five years from now instead of the issue today, and I wind up with code that tends to be more complex than necessary.