Synchronizing indices of function pointer table to table contents
In the embedded system I'm working on, we are using a table of function pointers to support proprietary Dynamic Libraries.
We have a header file that uses named constants (#define
) for the function pointer indices. These values are used in calculating the location in the table of the function's address.
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,
Proc开发者_StackOverflowess,
};
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 scheme to define the named constants in terms of their location in the array so that when the order of the functions changes, the constants will also change.
(Also, I would like the compiler or preprocessor to calculate the indices to avoid human mistakes like typeo's.)Using C99, you can use designated initializers:
enum {
ID_PRINT_FUNCTION = 1,
ID_READ_FUNCTION = 2,
ID_WRITE_FUNCTION = 3,
ID_PROCESS_FUNCTION = 4,
};
(The 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 deliberately reordered that - and the compiler sorts it out. Also, although I rewrote the '#define' values into 'enum' values, it would work with either.
Note that MSVC on Windows does not, AFAIK, support this notation. This means that I cannot use it in code that has to be ported between Linux and Windows - much to my exasperation.
See this answer for a way to coerce the preprocessor in doing it for you.
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;
};
My advice is: don't use C directly. Generate the .c and .h files from an input file written in some locally defined DSL as part of your build process. Then you only have a single source file to maintain (written in your DSL), and the DSL compiler ensures that the exported indices match the implementation of the array.
We employ this technique here. The 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 a foo.h
that looks like:
#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:
#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 brace nesting and commas to calculate the index of each element in the C array.
X-macros could 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, 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 your original functionality is that ID_PRINT_FUNCTION is now ID_Print_FUNCTION, etc.
Having to add an extra file is annoying, but you could avoid this by using #ifdef's and putting everything in the original header file, although this is less clear.
Few C programmers take advantage of the power of #undef
, specifically redefining a macro for your current purposes. This allows you to set up all of your data in a single readable table that can be updated and modified 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 more all for the low, low price of $0 with free S&H, you can do all of your function header mumbo jumbo all in 1 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 this header, preferably to the end of FUNCTION_MAP if you want to preserve backward compatibility as a library... otherwise you can just list them alphabetically.
精彩评论