3

I want to pass an arbitrary function and it's arguments to another function in C.

For example like the code below (which obviously does not work)

#include <stdio.h>

void doit(int (*f)(...), char *fname, ...)
{
    va_list argptr;
    va_start(argptr, fname);
    f(argptr)
    va_end(argptr);
}   

int func1(char *a, int b)
{
    fprintf(stderr, "func1 %s %d\n", a, b);
}   

int func2(char *a, int b, int c)
{
    fprintf(stderr, "func2 %s %d %d\n", a, b, c);
}

int main(int argc, char **argv)
{

    doit(func1, "func1", "blah", 10);

    return 0;
}   
steve landiss
  • 1,833
  • 3
  • 19
  • 30
  • http://stackoverflow.com/questions/840501/how-do-function-pointers-in-c-work – user3605508 Jul 07 '16 at 18:52
  • I understand how basic function pointers work. My question is to see if you can pass in variable arguments to a function. This in theory is possible because at the end of the day, the arguments are just passed on the stack. I can do this in assembler easily, wondering if there is a C trick to do it without resorting to assembly – steve landiss Jul 07 '16 at 18:59
  • The C standard does not mandate a stack. And typical ABIs don't use only the stack to pass arguments. – too honest for this site Jul 07 '16 at 19:35

2 Answers2

5

You need va_list forwarders to your functions if you want to have them participate in such a scheme. Something like:

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

void doit(int (*f)(va_list va), char *fname, ...)
{
    va_list argptr;
    va_start(argptr, fname);
    f(argptr);
    va_end(argptr);
}

int func1(const char *a, int b)
{
    fprintf(stderr, "func1 %s %d\n", a, b);
    return 0;
}

int func1_va(va_list va)
{
    const char * a = va_arg(va, const char*);
    int b = va_arg(va, int);
    return func1(a,b);
}

int func2(const char *a, int b, int c)
{
    fprintf(stderr, "func2 %s %d %d\n", a, b, c);
    return 0;
}

int func2_va(va_list va)
{
    const char *a = va_arg(va, const char*);
    int b = va_arg(va, int);
    int c = va_arg(va, int);
    return func2(a,b,c);
}

int main(int argc, char **argv)
{

    doit(func1_va, "func1", "blah", 10);
    doit(func2_va, "func2", "blahblah", 100, 200);

    return 0;
}

Output

func1 blah 10
func2 blahblah 100 200
WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • Thank you! Exactly what I was looking for. – steve landiss Jul 07 '16 at 19:04
  • @WhozCraig the first elements of the variadic arguments (`"func1"` and `"func2"`) get dropped, is that intentionally? – deamentiaemundi Jul 07 '16 at 19:24
  • @deamentiaemundi they're not dropped, and they're not part of the va_list. They're a formal paramter to the `fname` argument of `doit`, and are simply not used (the OP chose to do that, i was just following suit). Do they even need to be there? Not really. We could just-as-easily based `va_start` from `f`. – WhozCraig Jul 07 '16 at 19:26
  • @WhozCraig, Can't the wrapper functions func1_va and func2_va be made generic? Perhaps if they took in a char *fmt (like printf does) and parse the format and pass in the arguments to the function they are invoking? – steve landiss Jul 07 '16 at 19:28
  • That's why I asked (I never drop the first element, I don't want it wasted. But I have been raised in harsher times, when memory was not only sparse but horribly expensive, too ) – deamentiaemundi Jul 07 '16 at 19:30
  • @stevelandiss Far more difficult, and more platform-specific, than I think you may first realize. – WhozCraig Jul 07 '16 at 19:31
0

First: don't mess with va_args if you have no idea how arguments are passed or how activation records work.

Your best bet is to declare doit() with excessively many args:

void doit( void ( *p_func )(), int arg1, int arg2, int arg3, int arg4, int arg5 );

and just call ( *p_func )() with all those arguments. If the function being called doesn't reach for more than it needs, there won't be any trouble. Even if it does, it will just pull garbage values off the stack.

Cameron Skinner
  • 51,692
  • 2
  • 65
  • 86
  • This is [ABI](https://en.wikipedia.org/wiki/Application_binary_interface) specific. On x86-64 it probably would work, but there is no guarantee for other architectures. – Basile Starynkevitch Jul 07 '16 at 19:24