C有一个允许动态函数调用的变通方法吗?

时间:2016-10-31 07:17:57

标签: c function dynamic

我有read C不支持动态函数调用。我的程序有越来越多的测试用例作为单独的函数实现,如 -

int testcase1(void);
int testcase2(void);
int testcase3(void);

每次我添加一个新的测试用例时,我还必须将调用添加到我的主函数中,如 -

int main(int argc, char **argv){
  assert(!testcase1());
  assert(!testcase2());
  assert(!testcase3());
}

我更愿意调用类似assert(!testcase*())的内容,其中*匹配任何解析为程序中有效函数名的字符串。

你能想到一个更方便的解决方案吗?

4 个答案:

答案 0 :(得分:7)

如果您的所有测试用例都具有相同的签名,则可以使用函数指针数组:

void (*func[])() = { testcase1, testcase2 };

for (size_t i = 0; i < sizeof(func)/sizeof(func[0]); i++) {
   assert(!func[i]());
}

答案 1 :(得分:1)

当您添加新的测试用例时,最佳解决方案可能会编写一些额外的代码行 - 这确实不是一个大问题。我建议按照函数指针数组的方式进行操作,如另一个答案所示。

然而,只是为了表明如果你在问题上抛出丑陋的宏,C中的一切都是可能的,这里是一个不推荐的选择:

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>

#define TEST_CASES \                    // list of "x macros"
  X(testcase1) \
  X(testcase2) \
  X(testcase3)

#define X(func) bool func (void);       // declare function prototypes
  TEST_CASES
#undef X

bool (*const test_cases[])(void) =      // array of read-only function pointers
{
  #define X(func) &func,                // point at each function
    TEST_CASES
  #undef X
};

int main (void)
{
  for(size_t i=0; i<sizeof(test_cases)/sizeof(test_cases[0]); i++)
  {
    assert(test_cases[i]());
  }
}

bool testcase1 (void) { puts(__func__); return true; }
bool testcase2 (void) { puts(__func__); return true; }
bool testcase3 (void) { puts(__func__); return false; }

输出:

testcase1
testcase2
testcase3
Assertion failed!

对于每个新的测试用例,您只需编写一个函数定义,然后将其添加到“x macro”列表TEST_CASES。但是,你需要很好的理由在生产代码中引入这些丑陋的技巧!

答案 2 :(得分:0)

您可以使用function pointers。另请阅读closures(但C99或C11没有它们)和callbacks

许多operating systems提供dynamic loading。在POSIX操作系统(例如Linux或MacOSX)上,您可以使用dlopen&amp ;;在某些库(或程序可执行文件)中从其名称获取函数指针(实际上是地址)。 dlsym。其他操作系统可能提供类似的功能。

最后,您应该考虑使用metaprogramming技术通过某些脚本(或某些发出C代码的程序)生成测试main函数。因此,您可以编写一些内容,生成具有较长序列main的测试assert的C代码,并改进build procedure(例如Makefile如果使用make适当地运行那个专门的C代码生成器。详细信息当然特定于您的代码。您可以添加一些约定(例如,添加一些特殊注释以供您的测试生成器解析等)。

答案 3 :(得分:0)

我决定关注@Nominal Animal和@Basile Starynkevitch的方法。在mymainprog.c中,我添加了 -

int runtests(void){
    void *testh;
    int (*testp)(void);
    char *dlmsg;
    int rc;
    char funcname[8];
    int testnum;

    testh = dlopen("libsmtests.so", RTLD_LAZY);

    if (!testh){
        printf("%s\n", dlerror());
        return 1;
    } 
    dlerror();
    for (testnum =1; testnum < 1000; testnum++){
        sprintf(funcname,"testcase%d", testnum);
        *(void **) (&testp) = dlsym(testh, funcname);
        dlmsg = dlerror();
        if (dlmsg == NULL) {
            rc = (*testp)();
            printf("%s called, rc=%d\n", funcname, rc);
        }
    }
    dlclose(testh);
    return 0;
}

我将我的测试用例添加到一个单独的文件(testcases.c)中,如下所示 -

int testcase1(void){
    return [some testcase expression]
}   
int testcase2(void){
    return [another testcase expression]
}

然后将其编译为具有位置无关代码(-fPIC)到libsmtests.so的共享库。优点是输入略少,因为在向testcases.c添加新函数testNNNN()的实现之后,我不需要编写对int testcaseNNN(void)的调用的代码。