将函数指针表的索引与表内容同步

发布于 2024-08-31 23:08:04 字数 686 浏览 3 评论 0原文

在我正在开发的嵌入式系统中,我们使用函数指针表来支持专有的动态库。

我们有一个头文件,它使用命名常量(#define)作为函数指针索引。这些值用于计算函数地址表中的位置。

示例:

(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,
};

这是头文件:
export_table.h

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

我正在寻找一种方案来根据命名常量在数组中的位置来定义它们,以便当函数的顺序发生变化时,常量也会发生变化。
(另外,我希望编译器或预处理器计算索引以避免打字等人为错误。)

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,
  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 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.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

爱她像谁 2024-09-07 23:08:04

使用 C99,您可以使用指定的初始值设定项:

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

(C99 中允许使用尾随逗号;从技术上讲,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,
};

请注意,我故意重新排序 - 并且编译器将其排序。另外,虽然我将“#define”值重写为“enum”值,但它可以与任何一个一起使用。

请注意,据我所知,Windows 上的 MSVC 不支持此表示法。这意味着我无法在必须在 Linux 和 Windows 之间移植的代码中使用它——这让我很恼火。

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.

指尖上得阳光 2024-09-07 23:08:04

See this answer for a way to coerce the preprocessor in doing it for you.

腹黑女流氓 2024-09-07 23:08:04

您可以为每个函数指针定义一个包含命名元素的结构,而不是数组:

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

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;    
};
方圜几里 2024-09-07 23:08:04

我的建议是:不要直接使用C。从以某种本地定义的 DSL 编写的输入文件生成 .c 和 .h 文件作为构建过程的一部分。然后,您只需维护一个源文件(用 DSL 编写),并且 DSL 编译器可确保导出的索引与数组的实现相匹配。

我们在这里采用了这种技术。 DSL 基本上是一个带注释的 C 文件,如下所示:

@@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,
}

它将生成一个如下所示的 foo.h

#ifndef __HEADER_H
#define __HEADER_H

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

#endif

和一个如下所示的 foo.c

#include "foo.h"

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

:具有计算大括号嵌套和逗号的能力,以计算 C 数组中每个元素的索引。

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.

匿名的好友 2024-09-07 23:08:04

X 宏可以提供帮助。例如,创建一个新文件export_table_x.h,其中包含:

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

然后在export_table.h中使用:

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

在export_table.c中写入:

#include "export_table.h"

// ...

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

对原始功能的一项更改是ID_PRINT_FUNCTION现在是ID_Print_FUNCTION,等等

。文件很烦人,但是您可以通过使用 #ifdef 并将所有内容放入原始头文件中来避免这种情况,尽管这不太清楚。

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.

岁吢 2024-09-07 23:08:04

很少有 C 程序员会利用 #undef 的强大功能,专门为您当前的目的重新定义宏。这使您可以在单个可读表中设置所有数据,并且可以在 1 个位置更新和修改该表。

#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

但是等等,还有更多 0 美元的超低价格以及免费的 S&H,您可以在 1 个地方完成所有函数头的繁琐操作。

#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

现在,您可以将每个新函数添加到此标头顶部的一个位置,如果您想保留作为库的向后兼容性,最好添加到 FUNCTION_MAP 的末尾...否则您可以按字母顺序列出它们。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文