Если вы посмотрите на то, что расширяет va_start, вы увидите, что происходит:
va_start(argptr, format);
становится (примерно)
argptr = (va_list) (&format+1);
Если формат является типом значения, он помещается в стек прямо перед всеми аргументами переменной. Если формат является ссылочным типом, в стек помещается только адрес. Когда вы берете адрес ссылочной переменной, вы получаете адрес или исходную переменную (в данном случае временную AnsiString, созданную до вызова Broken), а не адрес аргумента.
Если вы не хотите передавать полные классы, вы можете либо передать указатель, либо ввести фиктивный аргумент:
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");
или
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");