2

I'm writing a unit test framework (see SO for more details). Or view the code at GitHub.

Safer Code describes a way to pass functions of arbitrary types.

But how do I call such a function without knowing its types beforehand? Assume f needs no input, so f() should work on its own.

Let's say I want to populate an array using an arbitrary generator function.

void* gen_array(fp gen, size_t size) {
    int i, len = gen_int() % 100;

    void* arr = GC_MALLOC(len * size);

    for (i = 0; i < len; i++) {
        arr[i] = gen(NULL);
    }

    return arr;
}

It should look something like this, but I get compiler errors:

gcc -o example example.c qc.c qc.h -lgc
In file included from example.c:1:
qc.h:21: error: expected declaration specifiers or ‘...’ before ‘size_t’
In file included from qc.c:1:
qc.h:21: error: expected declaration specifiers or ‘...’ before ‘size_t’
qc.c:23: error: conflicting types for ‘gen_array’
qc.h:21: error: previous declaration of ‘gen_array’ was here
qc.c: In function ‘gen_array’:
qc.c:29: warning: dereferencing ‘void *’ pointer
qc.c:29: error: too many arguments to function ‘gen’
qc.c:29: error: invalid use of void expression
qc.h:21: error: expected declaration specifiers or ‘...’ before ‘size_t’
make: *** [example] Error 1
Community
  • 1
  • 1
mcandre
  • 22,868
  • 20
  • 88
  • 147

5 Answers5

1

That page suggests you make the function pointer take a void*. So in order for your code to compile, you must pass it a void pointer:

typedef void* (*fp)(void*);

doit(fp f) {
   f(NULL);
}

And just make sure that the function that you're calling simply ignores the parameter.

Generally speaking, these generic function pointers are used for starting threads. The void pointer is simply a pointer to a struct that holds the actual parameters.

Mysticial
  • 464,885
  • 45
  • 335
  • 332
  • Thanks. But I still get compiler errors (see the new example). – mcandre Sep 23 '11 at 01:55
  • Based on the error you're getting, it looks like you haven't typedef'ed the function pointer properly. – Mysticial Sep 23 '11 at 01:57
  • Am I just using the wrong syntax, or is it impossible to call a generic function pointer without manually casting it? – mcandre Sep 23 '11 at 02:00
  • Can we see your declaration of the typedef for `fp`? It's possible to do generic function pointers (you have to use them for threads anyways). But when I say "generic", I don't mean function pointer with different parameters, I mean only with one `void*` which points to the "actual" parameters. – Mysticial Sep 23 '11 at 02:01
  • I'm not sure how much code to provide, so just try the gen_array() function at GitHub. https://github.com/mcandre/qc – mcandre Sep 23 '11 at 02:16
  • I tried your most recent typedef, but I still get compiler errors. Please try forking the GitHub code. https://github.com/mcandre/qc – mcandre Sep 23 '11 at 02:24
  • Ok, I see what you're doing. The problem is that the return value also needs to be the same type. So now it gets tricky, because you need to make a type that can store all those data types you have `bool`, `int`, `char`, `void`, `char*`, etc... One question: how do you plan to read these values after you've stored them into the array. You'll have lost all the type information. – Mysticial Sep 23 '11 at 02:29
  • Indeed. For now, I'll use a va_list and require that all property functions take varargs. – mcandre Sep 23 '11 at 02:38
  • Ah, I see what you're trying to do based on your previous question about the varargs. This is not possible in C. The way to do it is to print from within each of your function pointers. Then set all your function pointers to the same prototype. `void get_XXX()` Change the typedef to `typedef void (*fp)();` Now you can dump them all into an array. When you call each element, it will print what you want. – Mysticial Sep 23 '11 at 02:42
1

After thinking about some more I realize your problem your above code would never work. You are first calling trying to call a void function with no parameters with the parameter NULL. Next you would need your code to be more generic. I placed an example below of what I mean. Now using a global variable

#include <stdio.h>
#include <stdlib.h>

typedef void (*fp)(void);


void * GEN_ARRAY_TEMP;

int gen_int() {
    return 67;
}

void* gen_array(fp gen, size_t size) {
    int i, len = gen_int() % 100;

    void* arr = malloc(len * size);
    void* arr_end = arr + len * size;
    GEN_ARRAY_TEMP = arr;

    while (GEN_ARRAY_TEMP <= arr_end) {
        gen();
        GEN_ARRAY_TEMP+=size;
    }
    return arr;
}

void make_int() {
    (*(int*)GEN_ARRAY_TEMP) = 9;
}

int main() {
    int i;
    int * gen_int_array = (int*) gen_array(make_int, sizeof(int));
    for(i=0;i<67;i++) {
        printf("%d\n",gen_int_array[i]);
    }
}
rouzier
  • 1,160
  • 8
  • 19
  • Awesome! Can you rewrite this so that `gen_array()` doesn't need to pass anything to `gen()`, in other words, `make_int()` shouldn't have any arguments. – mcandre Sep 23 '11 at 02:46
  • Inorder for that to happen you would need to make use of global variables; – rouzier Sep 23 '11 at 02:50
  • Thanks, but blobal variables wouldn't work with complex data types; A user may want to create random arrays of arrays of arrays of doubles. – mcandre Sep 23 '11 at 02:54
  • It would work with any type as long you pass the right size of the type. Also if they pass the pointer to that data structure it will always work – rouzier Sep 23 '11 at 02:57
  • This will work with any basic data type, but imagine a user who wants to create random arrays of random arrays of some basic type. There would be a stack of gen_array / gen_array / gen_array / gen_basic_type. The problem is that while the outer gen_array is looping, its global variable is being overwritten by the inner gen_array calls. Not to mention that I don't want users of the framework to have to use pointer arithmetic in their custom generator functions. – mcandre Sep 23 '11 at 03:03
  • If you can refactor this so that the generator function is truly nullary (no globals) and gen_array blindly accepts the return type, storing it in an array and returning it, I would be greatly indebted. – mcandre Sep 25 '11 at 21:40
  • There are only two ways I can see doing this. Either accept global variables and emulate a stack in global space. Or your call backs accept have to accept a void pointer like the previous example I had. – rouzier Sep 30 '11 at 21:15
  • Globals won't work. A user who needs to generate arrays of arrays will crash the program, because the nested gen_array calls will overwrite the global variables. – mcandre Sep 30 '11 at 21:43
0

What would you need to do is wrap your function in a void function like so

#include <stdio.h>
typedef void (*fp)(void);

int sum(int x,int y) {return x+y;}

void doit(fp f) {
        f();
}

void func() {
        printf("Hello %d\n",sum(1,2));
}

int main() {
        doit(func);
}
rouzier
  • 1,160
  • 8
  • 19
  • I don't think wrapping in a void function will help, since the passed-in function should return things. Can you post a working example of the gen_array() function at GitHub? http://github.com/mcandre/qc – mcandre Sep 23 '11 at 02:15
0

You have two problems:

First, qc.h is missing a <stdlib.h> include. This is needed for use of size_t.

Second, in gen_array, you create a void *arr, then try to dereference it as an array (arr[i]). Since the compiler doesn't know the size of your array elements, it cannot fill the array. You must treat it as a char *, offset by arr + size * i, and pass it into gen rather than taking a return (returns also need to know the structure size):

// ...
char *arr = GC_MALLOC(len * size);

for (int i = 0; i < len; i++) {
    gen(arr + i * size, NULL);
}

return arr;

This will of course require changing the fp type definition.

bdonlan
  • 224,562
  • 31
  • 268
  • 324
  • Added `#include ` to qc.h: Check. Thanks, bro! The API will have users build complex generators by using gen_array(), i.e. I can't pass anything to `gen`. Rather, `gen()` must be called and the array set to its return value at index `i`. – mcandre Sep 23 '11 at 02:33
  • @mcandre: You cannot fill an array of variable element size through return values. You can either have `gen()` return a `void*` and fill a `void**`, or pass the address you want the return value placed in via a parameter. There's no working around this. – bdonlan Sep 23 '11 at 02:48
  • The goal is to create a hierarchy of nullary generators that can be combined in creative ways. user905927's `make_int` is very close to this goal, but it forces users of the framework to use wrap all generators with awkward pointer referencing and dereferencing code. – mcandre Sep 23 '11 at 03:00
  • Unfortunately C isn't well suited for those kind of higher-order programming tricks. You may find C++ to work a bit better for your needs (you can always use C++ code to _test_ C code, after all) – bdonlan Sep 23 '11 at 03:17
  • Hmm. Just for the sake of argument, how would C++ implement gen_array so that it doesn't need to pass values to generator functions, and doesn't need to use global variables? – mcandre Sep 23 '11 at 03:20
  • @mcandre, something like this: https://gist.github.com/1236709 Note that `std::function` is a C++0x feature, but the boost libraries have a version that works in all current major compilers – bdonlan Sep 23 '11 at 03:49
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/3718/discussion-between-bdonlan-and-mcandre) – bdonlan Sep 23 '11 at 03:49
0

For the case where your pointer to a function 'fp' is of type which takes no argument and returns void, in which case you should declare it as :

typedef void (*fp)();  

In the above case the call should be :

(*gen)();  

If your pointer to the function 'fp' is of type which takes 'void *' as argument and returns void, in which case you should declare it as :

typedef void (*fp)(void *);

In the above case the call should be :

(*gen)(NULL);  

or any other pointer variable you might want to pass.

As far as your example goes try this :

typedef void * (*fp)(void *);


void* gen_array(fp gen, size_t size) {
    int i, len = gen_int() % 100;

    void* arr = GC_MALLOC(len * size);

    for (i = 0; i < len; i++) {
        arr[i]  = (*gen)(NULL);
    }

    return arr;
}
Anoop Menon
  • 619
  • 1
  • 4
  • 12
  • Hmm, still getting the same compiler errors. Could you try forking the code at GitHub? https://github.com/mcandre/qc – mcandre Sep 23 '11 at 20:13
  • Could you please remove the dependency on 'gc' and paste the essence of your problem ? That would make it easier because it seems to be that you are having trouble understanding pointer to a function. Maybe a small sample test code would be more useful – Anoop Menon Sep 24 '11 at 02:57
  • I understand function pointers. The difficulty lies in calling a nullary function that returns an arbitrary/unknowable type, saving the value in an array, and somehow passing that to the property to test. I use BoehmGC because I don't want to have to think about memory right now, just syntax. As it stands, the GitHub code (https://github.com/mcandre/qc) seems to work. Wherever C is too un-dynamic a language, you'll find the user has to write awkward code inside a generator, printer, or property function. – mcandre Sep 24 '11 at 11:19