How to wrap a function with variable length arguments?

30,650

Solution 1

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;
}

Solution 2

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.

Solution 3

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);
    ...
}

Solution 4

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
    }
}

Solution 5

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.

Share:
30,650
prakash
Author by

prakash

Dad, Dreamer, Developer

Updated on July 09, 2022

Comments

  • prakash
    prakash almost 2 years

    I am looking to do this in C/C++.

    I came across Variable Length Arguments but this suggests a solution with Python & C using libffi.

    Now, if I want to wrap printf function with myprintf

    What I do is 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
    

    Any point where did I miss??

  • Mooing Duck
    Mooing Duck about 11 years
    C++03 could use boost::tuple which has ways to do the above safely.
  • Francesco Dondi
    Francesco Dondi almost 8 years
    I wouldn't even dare ever to write this, but I cannot not admire it.
  • John Strood
    John Strood over 7 years
    Can you shed some light on why "you cannot use printf with va_args" ? Why vprintf?
  • erco
    erco over 6 years
    @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.
  • Chris Reid
    Chris Reid almost 6 years
    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
    Chris Reid almost 6 years
    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]"
  • user7082181
    user7082181 almost 3 years
    1 you gotta use const char 2 you definetely cannot use fmt as a string buffer for vprintf...did you test this code ???
  • Andak
    Andak over 2 years
    Thanks! I tested this with the nios2-elf-gcc compiler and it works like a charm!