How can I wrap a function with variable length arguments?
Asked Answered
N

8

52

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?

Nihi answered 3/9, 2008 at 10:12 Comment(9)
The answer for this question is very different now that C++11 is out.Senskell
@MooingDuck Indeed, I added a Variadic templates answer, do you think there is a nicer way in C++11?Maxon
@MooingDuck A vararg function is not a variadic template function. They are different in nature and type.Solidus
@Solidus 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: en.cppreference.com/w/cpp/utility/variadic So I would be curious to understand the distinction you see in this case.Maxon
@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.Solidus
@Solidus 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.Maxon
Actually variadic functions are so not obsolete with C++11, that the same specification introduces a new function to expand their usability cplusplus.com/reference/cstdarg/va_copy :)Vikkivikky
@Solidus 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?Anaheim
@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.Solidus
E
69

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;
}
Expeditionary answered 3/9, 2008 at 14:30 Comment(3)
Can you shed some light on why "you cannot use printf with va_args" ? Why vprintf?Hazan
@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.Lindi
1 you gotta use const char 2 you definetely cannot use fmt as a string buffer for vprintf...did you test this code ???Werbel
M
18

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.

Maxon answered 24/7, 2013 at 14:45 Comment(2)
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.Ingeingeberg
Thanks! I tested this with the nios2-elf-gcc compiler and it works like a charm!Boccherini
S
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);
    ...
}
Scully answered 3/9, 2008 at 10:33 Comment(1)
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]"Ingeingeberg
B
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
    }
}
Brandenbrandenburg answered 26/2, 2014 at 17:1 Comment(1)
I wouldn't even dare ever to write this, but I cannot not admire it.Vikkivikky
E
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.

Eryn answered 3/9, 2008 at 10:20 Comment(1)
C++03 could use boost::tuple which has ways to do the above safely.Senskell
Z
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

Zulemazullo answered 3/9, 2008 at 10:19 Comment(0)
C
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);

Corroborate answered 30/4, 2012 at 7:55 Comment(0)
T
0

I had the same problem to create a wrapper around a given function from SqLite3 using variable-argument-list without having a va_list style of function available. I choose the *macro' solution with ... → __VA_ARGS__. In addition to get SAFER code I always add a pseudo-header for the macro. The ({...}) is a gnu-compiler extension to create a macro with return-value. My return-value is MK_ERROR or MK_OK

/// \ingroup Sq3LiteC_Misc_C_API
enum MkErrorE Sq3LiteVtabConfig (SQ3_LITE sq3lite, Sq3VtabE op, ...);
#define Sq3LiteVtabConfig(sq3lite, op, ...) ({                              \
  Sq3ErrorE errVal = sqlite3_vtab_config(sq3lite->nat, op, __VA_ARGS__);    \
  Sq3ErrorE_Check_Macro(sq3lite,errVal);                                    \
  if (unlikely(Sq3ErrorCheckI(errVal))) {                                   \
    Sq3SqLite3ErrorToMQ(MkOBJ(sq3lite), __func__,__FILE__,__LINE__);        \
    MK_ERROR;                                                               \
  } else {                                                                  \
    MK_OK;                                                                  \
  }                                                                         \
})
Thermolabile answered 29/12, 2023 at 21:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.