自动生成C语言函数指针表

发布于 2024-08-26 13:56:06 字数 1074 浏览 9 评论 0原文

我正在寻找一种方法来自动(作为编译/构建过程的一部分)生成 C 中的函数指针“表”。

具体来说,我想生成一个结构数组,例如:

typedef struct {
  void (*p_func)(void);
  char * funcName;
} funcRecord;

/* Automatically generate the lines below: */

extern void func1(void);
extern void func2(void);
/* ... */

funcRecord funcTable[] =
{
  { .p_func = &func1, .funcName = "func1" },
  { .p_func = &func2, .funcName = "func2" }
  /* ... */
};

/* End automatically-generated code. */

...其中 func1 和 func2在其他源文件中定义。

因此,给定一组源文件,其中每个文件都包含一个不带参数并返回 void 的函数,如何自动(作为构建过程的一部分)生成一个像上面那样包含每个函数的数组从文件中?我希望能够添加新文件并在重新编译时将它们自动插入到表中。

我意识到这可能无法单独使用 C 语言或预处理器来实现,因此请考虑任何常见的 *nix 风格工具(例如 make、perl、shell 脚本(如果必须的话))。

但为什么?

您可能想知道为什么有人想要这样做。我正在为常见数学例程库创建一个小型测试框架。在这个框架下,将会有许多小的“测试用例”,每个测试用例只有几行代码来执行每个数学函数。我希望每个测试用例都作为一个简短的函数存在于自己的源文件中。所有测试用例都将构建到单个可执行文件中,并且可以在调用可执行文件时在命令行上指定要运行的测试用例。 main() 函数将搜索表,如果找到匹配项,则跳转到测试用例函数。

自动化构建测试用例“目录”的过程可确保测试用例不会被遗漏(例如,因为有人忘记将其添加到表中),并使维护人员可以非常简单地添加新的测试用例未来(例如,只需在正确的目录中创建一个新的源文件)。

希望有人以前做过类似的事情。感谢 StackOverflow 社区!

I'm looking for a way to automatically (as part of the compilation/build process) generate a "table" of function pointers in C.

Specifically, I want to generate an array of structures something like:

typedef struct {
  void (*p_func)(void);
  char * funcName;
} funcRecord;

/* Automatically generate the lines below: */

extern void func1(void);
extern void func2(void);
/* ... */

funcRecord funcTable[] =
{
  { .p_func = &func1, .funcName = "func1" },
  { .p_func = &func2, .funcName = "func2" }
  /* ... */
};

/* End automatically-generated code. */

...where func1 and func2 are defined in other source files.

So, given a set of source files, each of which which contain a single function that takes no arguments and returns void, how would one automatically (as part of the build process) generate an array like the one above that contains each of the functions from the files? I'd like to be able to add new files and have them automatically inserted into the table when I re-compile.

I realize that this probably isn't achievable using the C language or preprocessor alone, so consider any common *nix-style tools fair game (e.g. make, perl, shell scripts (if you have to)).

But Why?

You're probably wondering why anyone would want to do this. I'm creating a small test framework for a library of common mathematical routines. Under this framework, there will be many small "test cases," each of which has only a few lines of code that will exercise each math function. I'd like each test case to live in its own source file as a short function. All of the test cases will get built into a single executable, and the test case(s) to be run can be specified on the command line when invoking the executable. The main() function will search through the table and, if it finds a match, jump to the test case function.

Automating the process of building up the "catalog" of test cases ensures that test cases don't get left out (for instance, because someone forgets to add it to the table) and makes it very simple for maintainers to add new test cases in the future (just create a new source file in the correct directory, for instance).

Hopefully someone out there has done something like this before. Thanks, StackOverflow community!

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

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

发布评论

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

评论(6

白况 2024-09-02 13:56:06

使用宏

如何创建一个宏列表,

#define FUNC_LIST \
  FUNC( func1 ) \
  FUNC( func2 ) \
  FUNC( func3 ) \
  FUNC( func4 ) \
  FUNC( func5 ) 

然后将 extern 定义扩展为,

#define FUNC( _name ) extern void _name(void);

FUNC_LIST

#undef FUNC

然后将表扩展为

#define FUNC( _name ) { .p_func = &_name, .funcName = #_name },

funcRecord funcTable[] = {
  FUNC_LIST
};

#undef FUNC

使用 dlsym(..)

如果您对测试函数有严格的命名约定,另一个建议是研究使用句柄设置为 RTLD_DEFAULT 的函数 dlsym 并编写一个在启动时尝试查找所有函数的函数。

例子

#include <stdio.h>
#include <dlfcn.h>

void test2() {
  printf("Second place is the first loser!\n");
}

void test42() {
  printf("Read The Hitchhikers Guide To The Galaxy!\n");
}

int main() {
  int i;
  for (i=1; i<100; i++) {
    char fname[32];
    void (*func)();
    sprintf(fname, "test%d", i);
    func = dlsym(RTLD_DEFAULT, fname);
    if (func)
      func();
  }
  return 0;
}

Using macros

How about making a macro list as

#define FUNC_LIST \
  FUNC( func1 ) \
  FUNC( func2 ) \
  FUNC( func3 ) \
  FUNC( func4 ) \
  FUNC( func5 ) 

and then expand the extern definitions as

#define FUNC( _name ) extern void _name(void);

FUNC_LIST

#undef FUNC

and then expand the table as

#define FUNC( _name ) { .p_func = &_name, .funcName = #_name },

funcRecord funcTable[] = {
  FUNC_LIST
};

#undef FUNC

Using dlsym(..)

If you have a strict naming convention for you test functions another suggestion is to look into using the function dlsym with the handle set to RTLD_DEFAULT and write a function that tries to look upp all functions at startup.

Example

#include <stdio.h>
#include <dlfcn.h>

void test2() {
  printf("Second place is the first loser!\n");
}

void test42() {
  printf("Read The Hitchhikers Guide To The Galaxy!\n");
}

int main() {
  int i;
  for (i=1; i<100; i++) {
    char fname[32];
    void (*func)();
    sprintf(fname, "test%d", i);
    func = dlsym(RTLD_DEFAULT, fname);
    if (func)
      func();
  }
  return 0;
}
独留℉清风醉 2024-09-02 13:56:06

如果您使用的是 MSVC,则可以使用

dumpbin /symbols  foo.obj > foo_symbols.txt

将所有符号名称(不仅仅是函数)放入文本文件中。然后解析生成的文件以提取函数名称。函数将是非 UNDEF 部分中的 External 符号。

或者,您可以将对象链接到临时 exe 或 dll,然后查看由该函数生成的 .MAP 文件。链接器来获取函数名称。

或者您可以编写自己的代码来解析 .obj 文件,它们采用修改后的 COFF 格式,定位符号表并对其进行解码并不困难。我对 COFF 格式进行了一次部分解码以获取符号表,并花了几天时间编写代码。 http://en.wikipedia.org/wiki/COFF

If you are using MSVC, you can use

dumpbin /symbols  foo.obj > foo_symbols.txt

To get all of the symbol names (not just functions) into a text file. Then parse the resulting file to extract the function names. Functions will be External symbols in a section that is not UNDEF

Alternatively, you could link your objects into a temporary exe or dll and then look at the .MAP file produced by the linker to get the function names.

Or you could write your own code to parse the .obj files, they are in a modified COFF format and it's not that difficult to locate the symbol table and decode it. I did a partial decode of the COFF format once to get at the symbol table and it took a couple of days to write the code. http://en.wikipedia.org/wiki/COFF

很糊涂小朋友 2024-09-02 13:56:06

听起来像是代码生成的工作。选择您选择的脚本语言,并弄清楚如何从适当的标头中提取函数名称并写出

{ .p_func = &functionname, .funcName = "functionname" },

然后告诉您构建系统生成标头头文件。在 make 中它可能看起来像

UTILITY_FUNCTION_HEADER:= func1.h func2.h func3.h
func_table.h: ${UTILITY_FUNCTION_HEADERS}
        write_header.sh > $@
        for file in @^; do extract_function_name.sh >> $@; done
        write_footer.sh >>$@

Sounds like a job for code generation. Pick a scripting language of you choice, and figure out how to extract the name of the function from the appropriate header and write out

{ .p_func = &functionname, .funcName = "functionname" },

Then tell you build system to generate the header header file. In make it might look like

UTILITY_FUNCTION_HEADER:= func1.h func2.h func3.h
func_table.h: ${UTILITY_FUNCTION_HEADERS}
        write_header.sh > $@
        for file in @^; do extract_function_name.sh >> $@; done
        write_footer.sh >>$@
只涨不跌 2024-09-02 13:56:06

关于提取问题,我想我会以某种方式标记我想要导出的函数,然后在构建过程中提取它。

您可以使用“语义宏”à la gettext(即除了向外部工具提供语义信息之外不执行任何操作的宏):

#define TEST_CASE(f) f

T TEST_CASE(f)(D x, ...)
{
        /* ... */
}

然后您可以使用 sed 或 awk 或您喜欢的任何内容轻松提取该宏,并以正确的方式创建列表以此为基础的格式。这是 awk 中的一些简单代码,因为这是我最了解的,但您可能想使用其他代码:

match($0, /TEST_CASE\([a-zA-Z_][a-zA-Z_0-9]*\)/) {
        name = substr($0, RSTART, RLENGTH)
        sub(/^TEST_CASE\(/, "", name)
        sub(/\)$/, "", name)
        funcs[name]
}

END {
        for (f in funcs)
                printf "func_type %s;\n", f
        print "funcRecord funcTable[] = {"
        for (f in funcs)
                printf "\t{ .p_func = %s, .funcName = \"%s\" },\n", f, f
        print "};"
}

如果您要对名称进行排序(对 bsearch()-ing 很有用),我建议使用三个过滤器:提取过滤器(sed 单行在这里是合适的),排序(1),然后是生成过滤器(我在这里使用 awk)。不过,您必须单独生成页眉/页脚,并进行两次传递或将提取结果存储在临时文件中,以便生成外部声明和数组条目。

我认为尝试提取具有给定原型的函数不是一个好主意,例如 void (void)。最好使用 typedef (在我的示例中为 func_type )和显式语义宏,恕我直言,它更健壮(对于更改,以及不同的编码风格,例如将返回类型单独放在一行与不放在一行)。

剩下要做的就是将该生成过程添加到您的 makefile 中,如 dmckee 的回复中所示(尽管我认为您实际上希望将所有生成的代码放入 .c 而不是 .h 中)。为了完整起见,这是我的版本:

TEST_SRCS=      test1.c test2.c test3.c

test_funcs.c: ${TEST_SRCS}
        echo '#include "test.h"' >$@
        awk -f extract_test_funcs.awk ${TEST_SRCS} >>$@

About the extraction problem, I think I'd flag the functions I want to export, somehow, then extract that during the build process.

You could use a "semantic macro" à la gettext (i.e. a macro that does nothing except provide semantic information to external tools):

#define TEST_CASE(f) f

T TEST_CASE(f)(D x, ...)
{
        /* ... */
}

Then you can easily extract that using sed or awk, or whatever you prefer, and make a list in the correct format based on that. Here's some simple code in awk since that's what I know best, but you might want to use something else:

match($0, /TEST_CASE\([a-zA-Z_][a-zA-Z_0-9]*\)/) {
        name = substr($0, RSTART, RLENGTH)
        sub(/^TEST_CASE\(/, "", name)
        sub(/\)$/, "", name)
        funcs[name]
}

END {
        for (f in funcs)
                printf "func_type %s;\n", f
        print "funcRecord funcTable[] = {"
        for (f in funcs)
                printf "\t{ .p_func = %s, .funcName = \"%s\" },\n", f, f
        print "};"
}

If you're going to sort the names (useful for bsearch()-ing), I'd recommend using three filters: an extraction filter (sed one-liner is appropriate here), sort(1), then a generation filter (I'd use awk here). You'd have to generate a header/footer separately, though, and make two passes or store the result of the extraction in a temporary file in order to generate both the extern declarations and the array entries.

I don't think it's a good idea to try to extract functions with a given prototype, e.g. a void (void). Better use a typedef (func_type in my example) and an explicit semantic macro, IMHO it's more robust (to changes, and also to different coding styles, e.g. putting the return type on a line by itself vs. not).

All that's left to do then is to add that generation pass to your makefile, as in dmckee's reply (though you'll actually want to put all that generated code in a .c rather than a .h, I think). For completeness, here's my version:

TEST_SRCS=      test1.c test2.c test3.c

test_funcs.c: ${TEST_SRCS}
        echo '#include "test.h"' >$@
        awk -f extract_test_funcs.awk ${TEST_SRCS} >>$@
悸初 2024-09-02 13:56:06

我倾向于用不同的方式来解决这个问题。当我开发一些东西时,我通常会想:正确的 C 文件,其中包含这样一个函数的代码,例如在 /project-devel/project/new_function.c 中。当我写这篇文章时,我倾向于创建 /project-devel/tests/test_new_function.c/project-devel/tests/make_new_function_test 你猜它会构建一个测试二进制文件,包括测试该新功能所需的一切。

我想问题是你能自动化这个吗?我不打算为它编写代码(因为我还没有想清楚),但您可以创建一个 perl/python 脚本来一次性创建 makefile、new function.c/.h 和测试引导程序。您可以通过对诸如 r"#include \\\"(\s+?).h\\\"" 之类的内容进行正则化来获取所需的包含内容,并创建一个测试功能“更新脚本”来重新-根据新信息创建测试用例/makefile。它仍然依赖于您编写实际的 test.c,但这对于建立的想法来说是微不足道的。

只是一个想法。

I tend to go about this a different way. When I develop something I usually think: right OK C file with code in it for such a function, say in /project-devel/project/new_function.c. When I write that, I tend to also create /project-devel/tests/test_new_function.c and /project-devel/tests/make_new_function_test which you've guessed it builds a test binary including everything necessary to test that new function.

The question I guess is could you automate this? I'm not about to write code for it (because I haven't thought it through) but you could create a perl/python script that creates the makefile, new function.c/.h and test bootstrap all in one go. You can grap the includes you need by regexing something like r"#include \\\"(\s+?).h\\\"" and create a test-funct "update script" that re-creates the test case/makefile based on new information. It still relies on you to write the actual test.c but that's trivial with the idea set up.

Just an idea.

梦在深巷 2024-09-02 13:56:06

C 编译器就是这样做的。在 Linux 系统下,尝试在已编译的 C 文件上调用 nm foo.o。它将打印出“符号表”,其中包括所有函数,包括静态函数。

编辑: 困难在于从源代码中提取信息(“有一个名为 'func1()' 的函数”)。它需要解析 C 源文件。 C 编译器已经做到了这一点(这就是它的工作),因此使用 C 编译器的输出是有意义的。输出是一个目标文件,其中包含包含该信息的符号表。所以我们的想法是解析 nm 的输出并生成定义“函数表”的 C 源文件。这可以通过 makefile 自动执行,以便始终生成该表。

The C compiler does that. Under a Linux system, try calling nm foo.o on a compiled C file. It will print out the "symbol table" which includes all functions, including static functions.

Edit: The difficulty lies in extracting the information ("there is a function named 'func1()'") from the source code. It requires parsing the C source file. The C compiler already does that (that's its job) so it makes sense to use the output of the C compiler. The output is an object file with a symbol table which contains that information. So the idea is to parse the output of nm and generate the C source file which defines the "table of functions". This can be automated from the makefile, so that the table is always generated.

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