Есть ли ошибки при использовании varargs со ссылочными параметрами?
-
03-07-2019 - |
Вопрос
У меня есть этот фрагмент кода (обобщенный)...
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, но я не совсем понимаю, что происходит.
Решение
Если вы посмотрите, во что расширяется 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");
Другие советы
Вот что говорит стандарт C ++ (18.7 - Другая поддержка времени выполнения) о va_start()
(выделено мое):
Ограничения, налагаемые ISO C второй параметр
<stdarg.h>
макрос в заголовкеparmN
отличаются в этом Международный стандарт. Параметр...
- это идентификатор крайний правый параметр в переменной список параметров функции определение (тот, что перед <=>). Если параметр <=> объявлен с функцией, массивом или ссылкой тип или с типом, который не совместим с типом, который приводит при передаче аргумента для которого нет параметра, поведение не определено .
Как уже упоминали другие, использование varargs в C ++ опасно, если вы используете его с непрямыми элементами C (и, возможно, даже другими способами). Р>
Тем не менее, я все еще использую printf () все время ...
Хороший анализ того, почему вы этого не хотите, можно найти в N0695
В соответствии со стандартами кодирования C ++ (Саттер, Александреску):
varargs никогда не следует использовать с C ++:
Они не являются безопасными по типу и имеют НЕПРАВИЛЬНОЕ поведение для объектов типа класса, что, вероятно, вызывает вашу проблему.
Вот мой простой обходной путь (скомпилированный с 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);
}
Примечание:
Поведение для типов классов в качестве аргументов varargs может быть неопределенным, но это соответствует моему опыту. Компилятор помещает sizeof (class) памяти класса в стек. Т.е. в псевдокоде:
alloca(sizeof(class));
memcpy(stack, &instance, sizeof(class);
Для действительно интересного примера того, как это используется очень креативно, обратите внимание, что вы можете передать экземпляр CString вместо LPCTSTR напрямую в функцию varargs, и это работает, и есть нет кастинга. Я оставляю это в качестве упражнения для читателя, чтобы выяснить, как они сделали эту работу.