cascaded calls to variadic functions in C

70 views Asked by At

I need to make a wrapper of vprintf, so that multiple implementations of a printf like function can be done. This code demonstrates the issue:

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

void print_fmt2(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);
}


void print_fmt1(const char *fmt, ...)
{
    va_list args, args_copy;
    va_start(args, fmt);
    va_copy(args_copy, args);
    print_fmt2(fmt, args_copy);
    va_end(args_copy);
    va_end(args);
    print_fmt2("\r\n");
}


int main ()
{
  print_fmt1("test return %s %u\r\n", "is", 0);
  return 0;
}

The above code, compiled with gcc 11.4, Linux, outputs:

test return 3447949600

But what I expect and want to achieve is:

test return is 0

The issue should be with usage of va_copy macro, but I can't figure out what is wrong.

I have to stick to C99 standard.

Any ideas?

Thanks.

4

There are 4 answers

0
Some programmer dude On

You can't do that.

That's why there's always two variants of the standard vararg functions: One vararg function, and one that takes a va_list argument.

Like in the example you show there's vprintf to use a va_list argument. You need to do the same with your functions if you want to "cascade" them.

So you need to have e.g.

void print_fmt2(const char *fmt, va_list args)
{
    vprintf(fmt, args);
}

void print_fmt1(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    print_fmt2(fmt, args);
    va_end(args);
}
0
gulpr On

You cant do that using functions.

But you can use function like macros to archive what you want:

void print_fmt2(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);
}


#define print_fmt1(...) \
{                               \
    print_fmt2(__VA_ARGS__);    \
    print_fmt2("\r\n");         \
}


int main ()
{
  print_fmt1("test return %s %u\r\n", "is", 0);
  return 0;
}

https://godbolt.org/z/7Mhs8oTd7

0
chux - Reinstate Monica On

I need to make a wrapper of vprintf ....

In addition to @Some programmer dude good answer, as vprintf() returns an int, so should print_fmt1(), print_fmt2().

*printf() functions return the number of characters transmitted, or a negative value if an output encoding error occurred ...

int print_fmt2(const char *fmt, va_list args) {
  return vprintf(fmt, args);
}

int print_fmt1(const char *fmt, ...) {
  va_list args;
  va_start(args, fmt);
  int retval = print_fmt2(fmt, args);
  va_end(args);

  if (retval >= 0) {
    int retval2 = printf("\r\n");
    retval = (retval2 < 0) ? retval2 : retval + retval2;
  }

  return retval;
}
0
Ian Abbott On

OP's original code has undefined behavior because vprintf was called with a parameter of type va_list that does not describe a list of arguments that matches the printf format string parameter. The format string contains %s followed by %u, so it is expecting the va_list parameter to describe an argument of type char * followed by an argument of type unsigned int. However, the va_list parameter actually describes another argument of type va_list.

Conversion of a list of variadic parameters to a va_list is a one way operation. There is no way to convert it back to a list of arguments in a function call. That would not make sense because the number of arguments in a function call is fixed at compile time (considering an argument of type va_list to be a single argument that happens to refer to another list of arguments). That is not a problem in practice, because there is nothing that you can do with a function that uses ... in its prototype that you cannot do with an equivalent function that uses a parameter of type va_list in its prototype. If you need to handle both forms, then just write two functions and make the function that has ... in its prototype be a simple wrapper that calls the equivalent function that has a parameter of type va_list to represent the variadic parameters.

For example:

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

void vprint_fmt2(const char *fmt, va_list args)
{
    vprintf(fmt, args);
}

void print_fmt2(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vprint_fmt2(fmt, args);
    va_end(args);
}

void vprint_fmt1(const char *fmt, va_list args)
{
    vprint_fmt2(fmt, args);
    print_fmt2("\r\n");
}

void print_fmt1(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vprint_fmt1(fmt, args);
    va_end(args);
}


int main ()
{
  print_fmt1("test return %s %u\r\n", "is", 0);
  return 0;
}

No va_copy() calls were required in the above. Here is a somewhat artificial use of va_copy() that changes vprint_fmt2() (and therefore print_fmt2()) to print the arguments twice:

void vprint_fmt2(const char *fmt, va_list args)
{
    va_list args_copy;
    va_copy(args_copy, args);
    vprintf(fmt, args_copy);
    va_end(args_copy);
    vprintf(fmt, args);
}