Search code examples
carraysfunctionpointersgame-engine

C function pointers with variable parameterisation and type


I'm working on a game engine written in C, trying to use as little of the standard library as possible. For my in game "objects" I've got my version of "abstract" structures, reminiscent of object oriented languages like Java and C#, but I've got a problem when it comes to having objects that do things, i.e. they contain a set of functions. Is there a way that I can store (as a member of a structure) an array of function pointers that is of variable size, type, and parameterisation? Can I give up just one of these requirements, i.e. have all operational functions of void type? Or should I go back to the drawing board entirely? I'm assuming this will have to be dynamically allocated as well, but as far as I know that's standard for these sort of things, right? Thanks in advance


Solution

  • You can safely convert any function pointer to any other function pointer type, so long as you convert it back to the correct type before calling it (C17 6.3.2.3.8). So you can have, for instance, an array of void (*)(void) pointers as your table of functions. You can dynamically allocate it like any other array. Note that a pointer to such an array will have type "pointer to pointer to function".

    Many people find it helpful to use typedefs to make C's function pointer syntax less confusing.

    typedef void (*funcptr_v_v)(void);
    typedef double (*funcptr_d_ii)(int, int);
    typedef char *(*funcptr_cp_i)(int);
    
    struct foo {
        funcptr_v_v *table;
    };
    
    double my_d_ii(int x, int y) { ... }
    char *my_cp_i(int z) { ... }
    
    struct foo f;
    f.table = malloc(10 * sizeof(funcptr_v_v));
    f.table[0] = (funcptr_v_v)my_d_ii;
    f.table[1] = (funcptr_v_v)my_cp_i;
    // ...
    printf("Calling my_cp_i(7), returning %s\n", ((funcptr_cp_i)(f.table[1]))(7));
    

    If you wanted to define struct foo without the typedef it would look like

    struct foo {
        void (**table)(void);
    };
    

    Of course, it is up to you to somehow keep track of the fact that the function pointer stored in f.table[1] actually points to a function which takes one int argument and returns char *, and cast it back correctly when calling it. There is no way to test the type at runtime. If you should cast it to a type that doesn't match the function it points to, and call it that way, the compiler probably won't stop you, but things will likely break horribly at runtime. So you'll want to design your code in a way that makes this hard to get wrong.

    "Feasible for performance" isn't really answerable; there's no way of knowing what your performance needs or bottlenecks are until you test. But creating and deallocation such an array is no more or less expensive than a dynamic array of any other type, and calling a function through a pointer is typically only slightly slower, if at all, than calling it directly.