20

I have been trying to pass variable arguments to other function in C but it is producing inconsistent result in different runtime environment as well as in different runs in same environment:

int main() 
{ 
    int result = myprintf("Something \n %d", 9); 
    return result;
}

int myprintf(const char *format, ...){
    printf("Something \n %d", 9); 
    printf("\n");
    va_list args;        
    va_start(args, format);    
    int result = printf(format,args);
    printf("\n");
    va_end(args);
    return result;
} 

And the result produced is:

WWW.FIRMCODES.COM 
 9
WWW.FIRMCODES.COM 
 438656664

I could not find the reason for "438656664".

Monk
  • 311
  • 1
  • 2
  • 5
  • 3
    You can't do that; you can pass the `args` only to functions that take `va_args` as argument. These have a `v` in their name: `vprintf`, `vfprintf`, `vsnprintf`. – M Oehm Apr 27 '16 at 06:06
  • 1
    In case if I need to create a wrapper over printf() function then how can I proceed?I was thinking that I can create a function and after some manipulation of format string I will call printf(). But as you suggested I should call the "v" family of functions ,right? – Monk Apr 27 '16 at 06:19
  • Yes, forwardng the `va_list` to the `v` family of functions is the way to go. You probably shouldn't manipulate the format string, though, at least not the format specifiers themselves. (You can get many compilers to check whether format string and arguments agree; gcc/clang do this via attributes, VC++ does this via SAL annotations. If you tamper with the format string, such checks will be pointless.) – M Oehm Apr 27 '16 at 06:24

4 Answers4

37

You cannot pass the variadic arguments to a variadic function. Instead, you must call a function that takes a va_list as argument. The standard library provides variants of printf and scanf that take a va_list; their names have the prefix v.

Your example should look like:

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

int printfln(const char *format, ...)
{
    int result;
    va_list args;

    va_start(args, format);
    result = vprintf(format, args);
    printf("\n");
    va_end(args);

    return result;
}

int main()
{
    int result = printfln("Something \n %d", 9);

    printf("(%d)\n", result);

    return 0;
}

There are some gotchas, for example when you want to call two v... function for printing to the screen and a log file: The v... function may exhaust the va_list, so you must pass in a fresh one to each call if your code should be portable.

M Oehm
  • 28,726
  • 3
  • 31
  • 42
  • 4
    va_copy can be used to obtain "a fresh one" before you exhaust it, and va_end must be used to release the copy. – Sam Liddicott Aug 09 '17 at 07:35
  • Strongly suggest you to read this https://stackoverflow.com/a/26919307/802708 , it demonstrate how to write a function with va_list argument. – tangxinfa Jul 20 '18 at 11:45
  • @tangxinfa: No need, I already know how to do that. (If your concern is that my answer doesn't address accessing the `va_list` several times, that's not what the question is about.) – M Oehm Jul 20 '18 at 11:50
  • "You cannot pass the variadic arguments to a variadic function." Unfortunately, you _can_ pass `va_list` to a variadic function, but you really do not want to because it most likely does not do what anyone wants. The compiler could/should probably detect that and print a warning... – MarcH Jul 21 '21 at 00:26
  • 1
    Well, @MarcH, that's the "you cannot hold the basketball and walk around" argument. Of course you _can_, but the ref won't let you. I agree that the sentence might have been better as "You should not ..." or with a qualification "... and expect it to behave like the original list of arguments". – M Oehm Jul 21 '21 at 04:48
  • I wish there were actually a referee in this case, it would have saved me a fair amount of time. – MarcH Jul 25 '21 at 02:28
8

For the C++ fellow also reading this. You can actually do it using pack expansion without using vprintf. This trick is quite handy when you need to wrap a method that takes the ellipsis (...) and not a va_list.

For instance:

template <class ... Args>
void foo(const char *format, Args ... args)
{
    printf(format, args...);
}

Here class ... Args is template parameter pack, Args ... args is function parameter pack, and args... is function parameter pack expansion.

Guillaume.P
  • 421
  • 1
  • 4
  • 12
0

Alternatively, you can simply use a wrapper macro:

#include <stdio.h>

#define myprintf(fmt, ...) ( printf("Something \n %d\n", 9), printf(fmt, __VA_ARGS__) )

int main (void) 
{ 
    int result = myprintf("Something \n %d\n", 9); 
    printf("%d\n", result);
}

Note the use of the comma operator to preserve the returned value of the right-hand printf call to the caller.

This isn't any less type safe than the (equally dangerous) stdarg.h variadic functions.

Lundin
  • 195,001
  • 40
  • 254
  • 396
-1

Just a simple demonstration and worked example with "a fresh one va_list" when you need to print/output-as-string a template string like constexpr const char* example = R"(template "%s")"; .

std::string print_templ(const char* format, ...)
{
    va_list args1;
    va_start(args1, format);
    va_list args2;
    va_copy(args2, args1);

    std::vector<char> str(std::vsnprintf(nullptr, 0, format, args1) + 1);
    va_end(args1);

    const int ret = std::vsnprintf(str.data(), str.size(), format, args2);
    va_end(args2);

    return std::string(str.begin(), str.begin()+ret);
}
Dmitry
  • 906
  • 1
  • 13
  • 32
  • why -1 ? I think mentioned code is quite useful when you need to generate string based on arguments. Ok, it's not pure C but closer to C++ – Dmitry Jun 29 '23 at 10:48