Подтвердить что ты не робот

Есть ли getchas с использованием varargs со ссылочными параметрами

У меня есть этот фрагмент кода (обобщенный)...

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

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

    va_end(argptr);
    return buff;
}

И, на основе того, что по ссылке предпочтительнее, где это возможно, я изменил ее таким образом.

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

Мой код вызова выглядит так: -

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

Но s1 содержит "Hello World", а s2 имеет "Hello (null)". Я думаю, это связано с тем, как работает va_start, но я не совсем уверен, что происходит.

4b9b3361

Ответ 1

Если вы посмотрите, что расширяет 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");

Ответ 2

Вот что говорит о стандарте С++ (18.7 - Other runtime support) о va_start() (выделение мое):

Ограничения, налагаемые ISO C второй параметр va_start() макрос в заголовке <stdarg.h> отличаются друг от друга Международный стандарт. Параметр parmN - это идентификатор самый правый параметр в переменной список параметров функции (тот, который был непосредственно перед ...). Если параметр parmN объявлен с помощью функции, массива или ссылки тип, или с типом, который не является совместимый с типом, который приводит при передаче аргумента, для которого нет параметра, поведение undefined.

Как уже упоминалось, использование varargs в С++ опасно, если вы используете его с элементами без прямых (и, возможно, даже другими способами).

Тем не менее, я все еще использую printf() все время...

Ответ 3

Хороший анализ, почему вы этого не хотите, находится в N0695

Ответ 4

В соответствии со стандартами кодирования С++ (Sutter, Alexandrescu):

varargs никогда не следует использовать с С++:

Они не безопасны для типов и имеют поведение UNDEFINED для объектов типа класса, что может вызвать проблемы.

Ответ 5

Боковое примечание:

Поведение типов классов как аргументов varargs может быть undefined, но оно соответствует моему опыту. Компилятор выталкивает sizeof (класс) памяти класса в стек. Т.е. в псевдокоде:

alloca(sizeof(class));
memcpy(stack, &instance, sizeof(class);

Для действительно интересного примера использования этого в очень творческом ключе, обратите внимание, что вы можете передать экземпляр CString вместо LPCTSTR для функции varargs напрямую, и он работает, и нет никакого участия в кастинге. Я оставляю это как упражнение для читателя, чтобы выяснить, как они это сделали.

Ответ 6

Здесь мое легкое обходное решение (скомпилировано с Visual С++ 2010):

void not_broken(const string& format,...)
{
  va_list argptr;
  _asm {
    lea eax, [format];
    add eax, 4;
    mov [argptr], eax;
  }

  vprintf(format.c_str(), argptr);
}