Есть ли ошибки, использующие varargs с опорными параметрами

c++ reference variadic-functions

8885 просмотра

6 ответа

У меня есть этот кусок кода (в обобщенном виде) ...

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, но я не совсем уверен, что происходит.

Автор: Roddy Источник Размещён: 11.11.2019 05:53

Ответы (6)


40 плюса

Решение

Если вы посмотрите, что расширяет 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");
Автор: Eclipse Размещён: 21.10.2008 03:25

14 плюса

Вот что говорит стандарт C ++ (18.7 - Другая поддержка времени выполнения) va_start()(выделено мое):

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

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

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

Автор: Michael Burr Размещён: 21.10.2008 03:34

4 плюса

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

Автор: MSalters Размещён: 21.10.2008 03:07

2 плюса

В соответствии со стандартами кодирования C ++ (Саттер, Александреску):

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

Они не являются безопасными по типу и имеют НЕОПРЕДЕЛЕННОЕ поведение для объектов типа класса, что, вероятно, вызывает вашу проблему.

Автор: lefticus Размещён: 21.10.2008 03:10

1 плюс

Вот мой простой обходной путь (скомпилированный с Visual C ++ 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);
}
Автор: York Размещён: 19.06.2013 02:56

0 плюса

Примечание:

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

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

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

Автор: Nick Размещён: 21.10.2008 06:24
Вопросы из категории :
32x32