50

I am looking to do this in C/C++. I came across Variable Length Arguments, but this suggests a solution with Python and C using libffi.

Now, if I want to wrap the printf function with myprintf.

I do it like below:

void myprintf(char* fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    printf(fmt, args);
    va_end(args);
}

int _tmain(int argc, _TCHAR* argv[])
{
    int a = 9;
    int b = 10;
    char v = 'C';
    myprintf("This is a number: %d and \nthis is a character: %c and \n another number: %d\n", a, v, b);
    return 0;
}

But the results are not as expected!

This is a number: 1244780 and
this is a character: h and
another number: 29953463

What did I miss?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
prakash
  • 58,901
  • 25
  • 93
  • 115
  • 2
    The answer for this question is _very_ different now that C++11 is out. – Mooing Duck Mar 04 '13 at 22:01
  • @MooingDuck Indeed, I added a `Variadic templates` answer, do you think there is a nicer way in C++11? – Shafik Yaghmour Jul 24 '13 at 14:47
  • @MooingDuck A vararg function is not a variadic template function. They are different in nature and type. – rubenvb Jul 24 '13 at 15:05
  • 1
    @rubenvb In this case I don't think the distinction matters, almost all references I have seen tout them as a direct replacement for variadic function: http://en.cppreference.com/w/cpp/utility/variadic So I would be curious to understand the distinction you see in this case. – Shafik Yaghmour Jul 24 '13 at 15:17
  • 1
    @shafik: how about the obvious code bloat for each instantiation? How about passing function pointers? There are some distinctions you need to be aware of. I'm not saying you shouldn't, I'm just saying no one deprecated variadic functions. – rubenvb Jul 24 '13 at 18:14
  • @rubenvb Fair points, there are trade-offs, no free lunch here. My answer just proposes that it is an option. I misunderstood your comment as implying they were not a viable replacement as opposed to there are trade-offs to consider. – Shafik Yaghmour Jul 25 '13 at 02:44
  • Actually variadic functions are so not obsolete with C++11, that the same specification introduces a new function to expand their usability http://www.cplusplus.com/reference/cstdarg/va_copy/ :) – Francesco Dondi Jun 17 '16 at 14:34
  • @rubenvb When you mean bloat, are you saying that every time I call the variadic template function with a different set of parameters, a new function would be generated? – Vishal Subramanyam Jun 23 '23 at 11:41
  • @VishalSubramanyam yes. For each set of parameters there will be an instantiation and I doubt any compiler currently can optimize that in any sensible way. – rubenvb Jun 30 '23 at 11:55

7 Answers7

68

The problem is that you cannot use 'printf' with va_args. You must use vprintf if you are using variable argument lists. vprint, vsprintf, vfprintf, etc. (there are also 'safe' versions in Microsoft's C runtime that will prevent buffer overruns, etc.)

You sample works as follows:

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

int _tmain(int argc, _TCHAR* argv[])
{
    int a = 9;
    int b = 10;
    char v = 'C';
    myprintf("This is a number: %d and \nthis is a character: %c and \n another number: %d\n", a, v, b);
    return 0;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mark
  • 10,022
  • 2
  • 38
  • 41
  • Can you shed some light on why "you cannot use `printf` with `va_args`" ? Why `vprintf`? – John Strood Oct 29 '16 at 17:10
  • 1
    @JohnStrood To answer this directly: because printf() does not accept a 'va_list' as an argument, it expects a variable number of arguments (e.g. "...") which is different. See the man page for printf() and vprintf(). No where does it say printf() accepts 'va_list' as an argument, only a variable number of arguments of the type the % format codes expect (int, long, float, etc). Only the vprintf() family of functions accept a va_list. – erco Oct 02 '17 at 23:17
  • 1 you gotta use const char 2 you definetely cannot use fmt as a string buffer for vprintf...did you test this code ??? – user7082181 May 28 '21 at 18:02
17

In C++11, this is one possible solution using variadic templates:

template<typename... Args>
void myprintf(const char* fmt, Args... args)
{
    std::printf(fmt, args...);
}

As rubenvb points out, there are trade-offs to consider. For example, you will be generating code for each instance which will lead to code bloat.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • Also be warned the argument format checking of printf and scanf family of functions does not work with templates. The format string is not checked. If you get the format string wrong It will not be caught at compile-time, but may segfault (crash) or have unknown behavior at run-time. – Chris Reid Jul 19 '18 at 23:42
  • Thanks! I tested this with the nios2-elf-gcc compiler and it works like a charm! – Andak Jan 14 '22 at 14:35
8

I am also unsure what you mean by pure.

In C++ we use:

#include <cstdarg>
#include <cstdio>

class Foo
{   
    void Write(const char* pMsg, ...);
};

void Foo::Write( const char* pMsg, ...)
{
    char buffer[4096];
    std::va_list arg;
    va_start(arg, pMsg);
    std::vsnprintf(buffer, 4096, pMsg, arg);
    va_end(arg);
    ...
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
David Sykes
  • 48,469
  • 17
  • 71
  • 80
  • You can add a compiler attribute to the function def and have the compiler check your format arguments. class Foo { __attribute__ ((format (printf, 2, 3))) void Write(const char* pMsg, ...); }; Foo f; f.Write( "%s %s %d %s" , "dog" , "cat", "horse", "pig"); "warning: format specifies type 'int' but the argument has type 'const char *' [-Wformat]" – Chris Reid Jul 19 '18 at 23:57
7

Actually, there's a way to call a function that doesn’t have a va_list version from a wrapper. The idea is to use assembler, do not touch arguments on the stack, and temporarily replace the function return address.

An example for Visual C x86. call addr_printf calls printf():

__declspec( thread ) static void* _tls_ret;

static void __stdcall saveret(void *retaddr) {
    _tls_ret = retaddr;
}

static void* __stdcall _getret() {
    return _tls_ret;
}

__declspec(naked)
static void __stdcall restret_and_return_int(int retval) {
    __asm {
        call _getret
        mov [esp], eax   ; /* replace current retaddr with saved */
        mov eax, [esp+4] ; /* retval */
        ret 4
    }
}

static void __stdcall _dbg_printf_beg(const char *fmt, va_list args) {
    printf("calling printf(\"%s\")\n", fmt);
}

static void __stdcall _dbg_printf_end(int ret) {
    printf("printf() returned %d\n", ret);
}

__declspec(naked)
int dbg_printf(const char *fmt, ...)
{
    static const void *addr_printf = printf;
    /* prolog */
    __asm {
        push ebp
        mov  ebp, esp
        sub  esp, __LOCAL_SIZE
        nop
    }
    {
        va_list args;
        va_start(args, fmt);
        _dbg_printf_beg(fmt, args);
        va_end(args);
    }
    /* epilog */
    __asm {
        mov  esp, ebp
        pop  ebp
    }
    __asm  {
        call saveret
        call addr_printf
        push eax
        push eax
        call _dbg_printf_end
        call restret_and_return_int
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
basin
  • 3,949
  • 2
  • 27
  • 63
1

Are you using C or C++? The next C++ version, C++0x, will support variadic templates which provide a solution to that problem.

Another workaround can be achieved by clever operator overloading to achieve a syntax like this:

void f(varargs va) {
    BOOST_FOREACH(varargs::iterator i, va)
        cout << *i << " ";
}

f(args = 1, 2, 3, "Hello");

In order to get this to work, the class varargs has to be implemented to override operator = that returns a proxy object which, in turn, overrides operator ,. However, making this variant type safe in current C++ isn't possible as far as I know since it would have to work by type erasure.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
0
void myprintf(char* fmt, ...)
{
    va_ list args;
    va_ start(args, fmt);
    printf(fmt, args); // This is the fault. "vprintf(fmt, args);"
                       // should have been used.
    va_ end(args);
}

If you're just trying to call printf, there's a printf variant called vprintf that takes the va_list directly: vprintf(fmt, args);

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
john
  • 1,323
  • 2
  • 19
  • 31
0

How do you mean a pure C/C++ solution?

The rest parameter (...) is supported cross platform in the C runtime.

va_arg, va_copy, va_end, va_start

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mark Ingram
  • 71,849
  • 51
  • 176
  • 230