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.)
a source to share
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.
a source to share
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.
a source to share
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.
a source to share
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.
a source to share