在C中自动生成函数指针表

时间:2010-03-19 21:54:36

标签: c unit-testing build-process function-pointers

我正在寻找一种自动(作为编译/构建过程的一部分)在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社区!

6 个答案:

答案 0 :(得分:6)

使用宏

如何将宏列表设为

#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(..)

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

实施例

#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;
}

答案 1 :(得分:3)

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

dumpbin /symbols  foo.obj > foo_symbols.txt

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

部分中的UNDEF个符号

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

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

答案 2 :(得分:3)

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

{ .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 >>$@

答案 3 :(得分:1)

关于提取问题,我想我会标记我想要导出的函数,不知何故,然后在构建过程中提取它。

您可以使用“语义宏”来获取文本(即除了向外部工具提供语义信息之外什么都不做的宏):

#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()有用),我建议使用三个过滤器:一个提取过滤器(这里适合单行程),排序(1),然后是生成过滤器(我在这里使用awk)。但是,您必须单独生成页眉/页脚,并进行两次传递或将提取结果存储在临时文件中,以生成extern声明和数组条目。

我认为尝试使用给定原型提取函数不是一个好主意,例如虚空(无效)。更好地使用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} >>$@

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

我倾向于采用不同的方式。当我开发一些我经常想到的东西时:对于这样一个函数,在其中包含代码的好的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和test bootstrap。您可以通过重新编写类似r"#include \\\"(\s+?).h\\\""的内容来编写所需的包含,并创建一个test-funct“更新脚本”,根据新信息重新创建测试用例/ makefile。它仍然依赖于你编写实际的test.c但这对于设置这个想法是微不足道的。

只是一个想法。