user2309803
user2309803

Reputation: 645

C is there a workaround to allow dynamic function calls?

I have read that C does not support dynamic function calls. My program has an ever growing number of test cases implemented as separate functions like -

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

Each time I add a new test case, I also have have to add the call to my main function like -

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

I would prefer to call something like assert(!testcase*()) where * matches any string which resolves to a valid function name in my program.

Can you think of a more convenient solution?

Upvotes: 0

Views: 281

Answers (4)

user2309803
user2309803

Reputation: 645

I decided to follow @Nominal Animal and @Basile Starynkevitch's approach. In mymainprog.c, I added -

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

I add my testcases to a separate file (testcases.c) like this -

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

and then compile it as a shared library with position-independant code (-fPIC) to libsmtests.so. The advantage is slightly less typing since I don't need to code a call to testNNNN() after adding the implementation of a new functionint testcaseNNN(void) to testcases.c

Upvotes: 0

You can use function pointers. Read also about closures (but C99 or C11 don't have them) and callbacks.

Many operating systems provide dynamic loading. On POSIX operating systems (such as Linux or MacOSX) you can get a function pointer (actually an address) from its name in some library (or in the program executable) using dlopen & dlsym. Other operating systems may provide similar functionalities.

At last, you should consider having your testing main function be generated by some script (or some program emitting C code), using metaprogramming techniques. So you would write something which generates the C code of your testing main having a long sequence of assert, and improve your build procedure (e.g. your Makefile if using make) to run appropriately that specialized C code generator. Details are of course specific to your code. You might add some conventions (e.g. add some special comment to be parsed by your test generator, etc...).

Upvotes: 0

Lundin
Lundin

Reputation: 214300

The best solution is likely to write a few extra lines of code when you add new test cases - it really isn't a big issue. I would recommend something along the lines of the function pointer array, as suggested in another answer.

However, just to show that everything is possible in C if you throw ugly macros at the problem, here is a not recommended alternative:

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

Output:

testcase1
testcase2
testcase3
Assertion failed!

For each new test case, you would only have to write a function definition and then add it to the "x macro" list TEST_CASES. However, you need very good reasons to introduce ugly tricks like these in production code!

Upvotes: 1

P.P
P.P

Reputation: 121407

If you all your testcases have same signature then you can use an array of function pointers:

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

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

Upvotes: 7

Related Questions