Pregunta

Tengo este fragmento 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 funciona va_start, pero no estoy exactamente seguro de lo que está sucediendo.

¿Fue útil?

Solución

Si observa a qué va_start se 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 variadic. 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, un AnsiString temporal creado 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");

Otros consejos

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 impone   el segundo parámetro para el   <stdarg.h> macro en el encabezado   parmN son diferentes en esto   Estándar internacional. El parámetro   ... es el identificador de la   parámetro más a la derecha en la variable   lista de parámetros de la función   definición (la que está justo antes de la   <=>).    Si el parámetro <=> se declara con una función, matriz o referencia   tipo, o con un tipo que no sea   compatible con el tipo que resulta   al pasar un argumento para el cual   no hay parámetro, el comportamiento   no está definido .

Como otros han mencionado, usar varargs en C ++ es peligroso si lo usas con elementos que no son C-rectos (y posiblemente incluso de otras maneras).

Dicho esto, todavía uso printf () todo el tiempo ...

Un buen análisis de por qué no quiere esto se encuentra en N0695

De acuerdo con los estándares de codificación C ++ (Sutter, Alexandrescu):

varargs nunca debe usarse con C ++:

No son de tipo seguro y tienen un comportamiento INDEFINIDO para los objetos de tipo de clase, lo que probablemente esté causando su problema.

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);
}

Nota al margen:

El comportamiento de los tipos de clase como argumentos varargs puede ser indefinido, pero es consistente en mi experiencia. El compilador empuja sizeof (clase) de la memoria de la clase en la pila. Es decir, en pseudocódigo:

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

Para un ejemplo realmente interesante de esto siendo utilizado 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 hay sin casting involucrado. Lo dejo como un ejercicio para que el lector descubra cómo hicieron que funcionara.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top