Variadic function pointer in C

143 Views Asked by At

I want to implement a function that take a function and its arguments then call it. I have search a lot and could not implement it with variadic function. I want a clean way to record stdio after calling a function for unit test.

Even if you want to suggest another way to record stdio, please help me solve the original question.

something like this, but working.

void
foo(void (*function)(...), ...) {
  va_list args;
  va_start(args, function);
  // This line is just to explain what i want
  function(args);
  va_end(args);
}


void
bar(int a, char *b) {
  printf("a = %d, b = %s\n", a, b);
}


int
main() {
    foo(bar, 2, "hello");
  return 0;
}

the above code ....................

4

There are 4 best solutions below

2
gulpr On

It does not work like you think. You need to pass the va_list to the function.

void foo(void (*function)(va_list), ...) {
  va_list args;
  va_start(args, function);
  function(args);
  va_end(args);
}


void bar(va_list list) 
{
    int a = va_arg(list, int);
    char *b = va_arg(list, char *);
    printf("a = %d, b = %s\n", a, b);
}


int main(void) {
    foo(bar, 2, "hello");
}

https://godbolt.org/z/db8nKPbrq

You can also use variadic macros (but of course it is done compile time):

#define foo(f, ...) f(__VA_ARGS__)


void bar(int a, char *b) {
    printf("a = %d, b = %s\n", a, b);
}

void zoo(int a, char *b) {
    printf("b = %s, a = %d\n", b, a);
}


void (*arr[2])(int, char *) = {zoo, bar};

int main() 
{
    for(int x = 0; x < 30; x++)
       foo(arr[x & 1], 5, "hello");
}

https://godbolt.org/z/j16focM9o

3
Ian Abbott On

For foo() to call a function pointer of a type that differs from the definition of the function being called, it needs to convert the function pointer to the correct type. Also, the number of arguments in each function call cannot be varied at run time. You may need to pass additional information to foo() to tell it how to call the function if it can choose several different types of function to call.

For example:

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

void
foo(int ftype, void (*function)(), ...) {
  va_list args;
  va_start(args, function);
  if (ftype == 1) {
      void (*fp)(int, char *) = (void (*)(int, char *))function;
      int a = va_arg(args, int);
      char *b = va_arg(args, char *);
      fp(a, b);
  }
  va_end(args);
}


void
bar(int a, char *b) {
  printf("a = %d, b = %s\n", a, b);
}


int
main() {
    foo(1, (void (*)())bar, 2, "hello");
  return 0;
}

EDIT 1: Originally, my call to foo was foo(1, bar, 2, "hello");. That is OK for C17, but for C23 (not yet published at the time of writing), the argument bar needs to be converted by a type-cast as shown above. (For C17, the type-cast on function in the initializer for fp in function foo was also not needed, but I originally added it anyway.)

0
zwol On

This is not possible in C at runtime. Calling a function whose argument list is not known at compile time requires hand-written assembly language.

For problems like this I usually recommend libffi, which contains that hand-written assembly language wrapped up in a nice API, but your problem is harder than what libffi can handle. The trouble is that you cannot even know how many arguments have been passed to a C variadic function, never mind the types, unless you know which function it is and how it interprets its arguments. To illustrate the problem, here's some standard (C+POSIX) variadic functions annotated with the only way to know when to stop calling va_arg for each...

// parse the format string in the first argument
void printf(const char *, ...);
// parse the format string in the *second* argument
void fprintf(FILE *, const char *, ...);
// look for O_CREAT in the flags passed as the second argument
// some OSes have added other flags that also require the third arg
int open(const char *, int, ...);
// last variadic arg is a null pointer
int execlp(const char *, const char *, ...);

Therefore, the only way to handle any function call is to have one wrapper for each function you want to wrap, that knows how to handle that function's arguments. But look on the bright side: if you do it that way, you can use vprintf and therefore you won't have to reimplement the printf format string parser yourself in assembly language. In fact, as long as there's a way to translate each call to a wrapper into a non-variadic call to the wrapped function, you can do the whole thing in C. (For execlp use execvp, for open pass a third argument even when it won't be used, etc.)

It is possible to write a semi-generic wrapper in assembly language, but it won't work for all function calls. The hardest constraint is that there's no way to make it work if any of the arguments to the wrapper, including both arguments to be consumed by the wrapper and arguments to be consumed by the wrapped function, were passed on the stack. This is because, like I said above, there's no way for a generic wrapper to know how many arguments there are, so there's no way to know how much data the wrapper should copy from its own incoming argument area to the argument area for the call to the wrapped function. Depending on the ABI, it may also be impossible to wrap calls to functions that take floating-point arguments or return a floating-point value, because generically you don't know whether there are any, and that might mean you don't know which registers hold which arguments anymore.

Here is an example semi-generic wrapper, in pseudo-C:

any_int log_call(const char *name, void (*fn)(...), ...)
{
    log_entry(name);
    any_int rv = fn(...);
    log_exit(name);
    return rv;
}

and in actual assembly language for the x86-64 ELF ABI, assuming that all the arguments are in the integer arg registers. Under that assumption, it's not necessary to know how many arguments there really were, because there are a fixed number of integer arg registers, so the wrapper can just assume they are all live. If the caller didn't put an argument in %r9 on entry, whatever garbage value was in there will get copied to %rcx and then the wrapped function won't look at it. This logic breaks down if the args spill onto the stack.

I think it would be possible to handle floating point arguments and/or return values on this CPU and ABI, but it would be significantly more work and this is already long enough.

# any_int log_call(const char *name, any_int (*fn)(...), ...)
        .globl log_call
        .type log_call, @function
log_call:
        # Push all six call-preserved registers and also %r10.
        # The call-preserved registers will be used to hold onto
        # the integer arguments to this function. %r10 is pushed
        # partially because the easiest way to keep the stack
        # properly aligned is to push an odd number of registers,
        # partially because it _might_ be a static chain pointer.
        push %rbp
        push %rbx
        push %r15
        push %r14
        push %r13
        push %r12
        push %r10

        # Save all the integer argument registers in call-preserved
        # regs; the first two we need ourselves, the rest might hold
        # args for the wrapped function.
        mov  %rdi, %rbp
        mov  %rsi, %rbx
        mov  %rdx, %r12
        mov  %rcx, %r13
        mov  %r8,  %r14
        mov  %r9,  %r15

        # Work done by the wrapper before the call.  At this point
        # 'name' is still in %rdi and the hypothetical static chain
        # pointer is still in %r10.
        call log_entry

        # Emplace arguments to the wrapped function and call it.
        # Note how we shift them so that what was our third arg is
        # the wrapper's first arg, and so on. %r10 is not call-
        # preserved and might have been a static chain pointer.
        # %eax must be cleared in case the wrapped function was variadic.
        mov  (%rsp), %r10
        mov  %r12, %rdi
        mov  %r13, %rsi
        mov  %r14, %rdx
        mov  %r15, %rcx
        xor  %eax, %eax
        call *%rbx

        # Save all the registers that might hold the wrapped
        # function's return value. The stashed arguments to the
        # wrapped function are no longer needed at this point.
        mov  %rax, %r12
        mov  %rdx, %r13

        # Work done by the wrapper after the call.  Must restore
        # 'name' to %rdi to be the first arg to log_exit, and, again,
        # the static chain pointer.
        mov  (%rsp), %r10
        mov  %rbx, %rdi
        call log_exit

        # Put the wrapped function's return value back in the
        # return value registers, clear our own stack frame, return.
        mov  %r12, %rax
        mov  %r13, %rdx

        pop  %r10
        pop  %r12
        pop  %r13
        pop  %r14
        pop  %r15
        pop  %rbx
        pop  %rbp

        ret
        .size log_call, .-log_call
0
Eric Postpischil On

I want a clean way to record stdio after calling a function for unit test.

The way to do this is to declare a function in the same way that printf or fprintf is declared. Then your function can define va_list args; as you show, and it can use the standard C library functions vsnprintf or vsprintf to format the desired output strings into a buffer and/or the functions vprintf or vfprintf to write them to a stream.

The vprintf, vfprintf, vsnprintf, and vsprintf functions take a va_list argument, so you can pass that directly without needing to reconstruct the original arguments in a new function call.

Note that these routines manipulate the va_list, so you need to use va_end on it after they return and use va_start to restart argument processing if you want to do it a second time, or you need to use va_copy before the call to make a copy.