Synchronizing function pointer table indexes with table contents

In the embedded system I'm working on, we use a function pointer table to support our own dynamic link libraries.

We have a header file that uses named constants ( #define

) for indexes of function pointers. These values ​​are used when calculating the location in the function address table.

Example:

(export_table.c)

// Assume each function in the table has an associated declaration
typedef void (*Function_Ptr)(void);

Function_Ptr    Export_Function_Table[] =
{
  0,
  Print,
  Read,
  Write,
  Process,
};

      

Here is the header file:
export_table.h

#define ID_PRINT_FUNCTION 1
#define ID_READ_FUNCTION  2
#define ID_WRITE_FUNCTION 3
#define ID_PROCESS_FUNCTION 4

      

I'm looking for a schema for defining named constants in terms of their location in an array, so that when the order of the functions changes, the constants change as well.
(Also, I would like the compiler or preprocessor to compute the indices to avoid human errors like typeo.)

+2


a source to share


6 answers


See this answer for a way to force the preprocessor to do this for you.



+1


a source


Using C99, you can use designated initializers:

enum {
    ID_PRINT_FUNCTION   = 1,
    ID_READ_FUNCTION    = 2,
    ID_WRITE_FUNCTION   = 3,
    ID_PROCESS_FUNCTION = 4,
};

      

(Trailing comma is allowed in C99, technically it is not in C90.)



// Assume each function in the table has an associated declaration
typedef void (*Function_Ptr)(void);

Function_Ptr Export_Function_Table[] =
{
    [ID_READ_FUNCTION]    = Read,
    [ID_WRITE_FUNCTION]   = Write,
    [ID_PROCESS_FUNCTION] = Process,
    [ID_PRINT_FUNCTION]   = Print,
};

      

Note that I have intentionally reordered this - and the compiler is sorting it. Also, although I rewrote the "#define" values ​​to the "enum" values, it will work with.

Note that MSVC on Windows does not AFAIK this designation. This means that I cannot use it in code that needs to be ported between Linux and Windows - much to my annoyance.

+3


a source


Instead of an array, you can define a structure with named elements for each function pointer:

struct call_table {
    Function_Ptr reserved;    
    Function_Ptr print_fcn;    
    Function_Ptr read_fcn;    
    Function_Ptr write_fcn;    
    Function_Ptr process_fcn;    
};

      

+1


a source


My advice: don't use C directly. Create .c and .h files from an input file recorded in your local DSL directory . as part of your build process. Then you only have one source file to maintain (recorded in your DSL), and the DSL compiler makes sure the exported indices match the array implementation.

We are using this technique here. DSL is basically an annotated C file that looks something like this:

@@generate .h
#ifndef __HEADER_H
#define __HEADER_H

@export FN_LIST

#endif
@@generate .c
#include "foo.h"

@define FN_LIST
int myArray[] = 
{
    @FN_INDEX_FOO
    12,
    @FN_INDEX_BAR
    13,
    @FN_INDEX_BAZ
    14,
}

      

which would generate foo.h

which looks like this:

#ifndef __HEADER_H
#define __HEADER_H

#define FN_INDEX_FOO 0
#define FN_INDEX_BAR 1
#define FN_INDEX_BAZ 2

#endif

      

and a foo.c

that looks like this:

#include "foo.h"

int myArray[] = 
{
    /* FN_INDEX_FOO = 0 */
    12,
    /* FN_INDEX_BAR = 1 */
    13,
    /* FN_INDEX_BAZ = 2 */
    14,
}

      

The parser has some ability to count parentheses and commas to compute the index of each element in the C array.

+1


a source


X macros can help. For example, create a new file export_table_x.h containing:

X_MACRO(Print),
X_MACRO(Read),
X_MACRO(Write),
X_MACRO(Process)

      

Then in your export_table.h file use:

#define X_MACRO(x) ID_ ## x ## _FUNCTION
enum
{
    ID_INVALID = 0,
    #include "export_table_x.h"
};
#undef X_MACRO

      

And in export_table.c write:

#include "export_table.h"

// ...

#define X_MACRO(x) x
Function_Ptr Export_Function_Table[] =
{
    0,
    #include "export_table_x.h"
};

      

One change to the original functionality is that ID_PRINT_FUNCTION is now ID_Print_FUNCTION, etc.

Having an extra file is annoying, but you can avoid it by using #ifdef and putting everything in the original header file, although this is less clear.

0


a source


Few C programmers take advantage of #undef

, in particular, overriding the macro for your current purposes. This allows you to customize all of your data in one readable table that can be updated and changed in 1 place.

#define FUNCTION_MAP { \
   MAP(ID_NOP_FUNCTION,     NULL), \
   MAP(ID_PRINT_FUNCTION,   Print), \
   MAP(ID_READ_FUNCTION,    Read), \
   MAP(ID_WRITE_FUNCTION,   Write), \
   MAP(ID_PROCESS_FUNCTION, Process), \
}

#define MAP(x,y) x
enum function_enums FUNCTION_MAP;
#undef MAP

#define MAP(x,y) y
Function_Ptr Export_Function_Table[] = FUNCTION_MAP;
#undef MAP

      

but wait, there's most of all for a low, low price of $ 0 with free S&H, you can make all your functions a mumbo jumbo header all in one place.

#define FUNCTION_MAP  \
   /*   ENUM              Function, return, Arguments ...                  */ \
   MAP(ID_PRINT_FUNCTION,   Print,  int,    char *fmt, va_list *ap          ) \
   MAP(ID_READ_FUNCTION,    Read,   int,    int fd,    char *buf, size_t len) \
   MAP(ID_WRITE_FUNCTION,   Write,  int,    int fd,    char *buf, size_t len) \
   MAP(ID_PROCESS_FUNCTION, Process,int,    int                             )

//function enums
#define MAP(x,y,...) x,
enum function_enums { FUNCTION_MAP };
#undef MAP

//function pre-definitions with unspecified number of args for function table
#define MAP(x,fn_name,ret,...) ret fn_name();
FUNCTION_MAP
#undef MAP

//function tables with unspecified number of arguments
#define MAP(x,y,...) y,
typedef int (*Function_Ptr)();
Function_Ptr Export_Function_Table[] = { FUNCTION_MAP };
#undef MAP

//function definitions with actual parameter types
#define MAP(x,fn_name,ret,...) ret fn_name(__VA_ARGS__);
FUNCTION_MAP
#undef MAP

//function strings ... just in case we want to print them
#define MAP(x,y,...) #y,
const char *function_strings[] = { FUNCTION_MAP };
#undef MAP

//function enum strings ... just in case we want to print them
#define MAP(x,y,...) #x,
const char *function_enum_strings[] = { FUNCTION_MAP };
#undef MAP
#undef FUNCTION_MAP

      

Now you can just add each new function in one place at the top of that header, preferably before the end of the FUNCTION_MAP if you want to keep backward compatibility as a library ... otherwise you can just list them alphabetically.

0


a source







All Articles