c++ - parameter - ¿Hay errores usando varargs con parámetros de referencia?
c++ variadic templates (6)
Aquí está mi solución fácil (compilada con 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);
}
Tengo este pedazo de código (resumido) ...
AnsiString working(AnsiString format,...)
{
va_list argptr;
AnsiString buff;
va_start(argptr, format);
buff.vprintf(format.c_str(), argptr);
va_end(argptr);
return buff;
}
Y, sobre la base de que se prefiere pasar por referencia siempre que sea posible, lo cambié de esta manera.
AnsiString broken(const AnsiString &format,...)
{
... the rest, totally identical ...
}
Mi código de llamada es así: -
AnsiString s1, s2;
s1 = working("Hello %s", "World");
s2 = broken("Hello %s", "World");
Pero, s1 contiene "Hello World", mientras que s2 tiene "Hello (null)". Creo que esto se debe a la forma en que va_start funciona, pero no estoy exactamente seguro de lo que está pasando.
De acuerdo con C ++ Coding Standards (Sutter, Alexandrescu):
varargs nunca debe usarse con C ++:
No son de tipo seguro y tienen un comportamiento INDEFINIDO para objetos del tipo de clase, lo que probablemente sea el causante de su problema.
Nota al margen:
El comportamiento de los tipos de clase como argumentos varargs puede no estar definido, pero es consistente en mi experiencia. El compilador introduce sizeof (clase) de la memoria de la clase en la pila. Es decir, en pseudo-código:
alloca(sizeof(class));
memcpy(stack, &instance, sizeof(class);
Para que un ejemplo realmente interesante de esto se utilice de una manera muy creativa, observe que puede pasar una instancia de CString en lugar de un LPCTSTR a una función varargs directamente, y funciona, y no hay conversión. Lo dejo como ejercicio para que el lector descubra cómo hicieron ese trabajo.
Un buen análisis de por qué no quieres esto se encuentra en N0695
Si observa lo que va_start expande, verá lo que está sucediendo:
va_start(argptr, format);
se convierte (aproximadamente)
argptr = (va_list) (&format+1);
Si el formato es un tipo de valor, se coloca en la pila justo antes de todos los argumentos variados. Si el formato es un tipo de referencia, solo la dirección se coloca en la pila. Cuando toma la dirección de la variable de referencia, obtiene la dirección o la variable original (en este caso, una AnsiString temporal creada antes de llamar a Broken), no la dirección del argumento.
Si no desea pasar clases completas, sus opciones son pasar por puntero o poner un argumento ficticio:
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");
o
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");
Esto es lo que dice el estándar C ++ (18.7 - Otro soporte de tiempo de ejecución) sobre va_start()
(énfasis mío):
Las restricciones que ISO C coloca en el segundo parámetro para la macro
va_start()
en el encabezado<stdarg.h>
son diferentes en esta norma internacional. El parámetroparmN
es el identificador del parámetro más a la derecha en la lista de parámetros variables de la definición de la función (la que está justo antes de...
). Si el parámetroparmN
se declara con una función, matriz o tipo de referencia, o con un tipo que no es compatible con el tipo que resulta al pasar un argumento para el que no hay parámetro, el comportamiento no está definido .
Como otros han mencionado, el uso de varargs en C ++ es peligroso si lo usa con ítems que no sean C-rectos (y posiblemente incluso de otras maneras).
Dicho eso, sigo usando printf () todo el tiempo ...