Are there gotchas using varargs with reference parameters

Roddy picture Roddy · Oct 21, 2008 · Viewed 9.3k times · Source

I have this piece of code (summarized)...

AnsiString working(AnsiString format,...)
{
    va_list argptr;
    AnsiString buff;

    va_start(argptr, format);
    buff.vprintf(format.c_str(), argptr);

    va_end(argptr);
    return buff;
}

And, on the basis that pass by reference is preferred where possible, I changed it thusly.

AnsiString broken(const AnsiString &format,...)
{
... the rest, totally identical ...
}

My calling code is like this:-

AnsiString s1, s2;
    s1 = working("Hello %s", "World");
    s2 = broken("Hello %s", "World");

But, s1 contains "Hello World", while s2 has "Hello (null)". I think this is due to the way va_start works, but I'm not exactly sure what's going on.

Answer

Eclipse picture Eclipse · Oct 21, 2008

If you look at what va_start expands out to, you'll see what's happening:

va_start(argptr, format); 

becomes (roughly)

argptr = (va_list) (&format+1);

If format is a value-type, it gets placed on the stack right before all the variadic arguments. If format is a reference type, only the address gets placed on the stack. When you take the address of the reference variable, you get the address or the original variable (in this case of a temporary AnsiString created before calling Broken), not the address of the argument.

If you don't want to pass around full classes, your options are to either pass by pointer, or put in a dummy argument:

AnsiString working_ptr(const AnsiString *format,...)
{
    ASSERT(format != NULL);
    va_list argptr;
    AnsiString buff;

    va_start(argptr, format);
    buff.vprintf(format->c_str(), argptr);

    va_end(argptr);
    return buff;
}

...

AnsiString format = "Hello %s";
s1 = working_ptr(&format, "World");

or

AnsiString working_dummy(const AnsiString &format, int dummy, ...)
{
    va_list argptr;
    AnsiString buff;

    va_start(argptr, dummy);
    buff.vprintf(format.c_str(), argptr);

    va_end(argptr);
    return buff;
}

...

s1 = working_dummy("Hello %s", 0, "World");