2009-02-17 14 views
16

A previous question mostró una buena manera de imprimir en una cadena. La respuesta implicada va_copy:va_copy - ¿portar a C++ visual?

std::string format (const char *fmt, ...); 
{ 
    va_list ap; 
    va_start (ap, fmt); 
    std::string buf = vformat (fmt, ap); 
    va_end (ap); 
    return buf; 
} 


std::string vformat (const char *fmt, va_list ap) 
{ 
    // Allocate a buffer on the stack that's big enough for us almost 
    // all the time. 
    s ize_t size = 1024; 
    char buf[size]; 

    // Try to vsnprintf into our buffer. 
    va_list apcopy; 
    va_copy (apcopy, ap); 
    int needed = vsnprintf (&buf[0], size, fmt, ap); 

    if (needed <= size) { 
     // It fit fine the first time, we're done. 
     return std::string (&buf[0]); 
    } else { 
     // vsnprintf reported that it wanted to write more characters 
     // than we allotted. So do a malloc of the right size and try again. 
     // This doesn't happen very often if we chose our initial size 
     // well. 
     std::vector <char> buf; 
     size = needed; 
     buf.resize (size); 
     needed = vsnprintf (&buf[0], size, fmt, apcopy); 
     return std::string (&buf[0]); 
    } 

}

El problema que estoy teniendo es que el código anterior no puerto a Visual C++, ya que no proporciona va_copy (o incluso __va_copy). Entonces, ¿alguien sabe cómo portar con seguridad el código anterior? Presumiblemente, necesito hacer una copia de va_copy porque vsnprintf modifica de forma destructiva la va_va_list pasada.

+0

he implementado cosas similares en VC++ y nunca he necesitado usar 'va_copy()'. ¿Qué sucede cuando lo intentas sin usar la copia? –

+1

Quién sabe ... Puede parecer que funciona. Incluso si lo hace, no significa que sea seguro. – user48956

+1

Aparentemente va_copy() es una cosa C99. Para VC++, estará bien utilizando la lista va_ original más de una vez sin preocuparse por una copia. vsnprintf no intentará modificar la lista aprobada. –

Respuesta

12

Usted debe ser capaz de salirse con sólo hacer una asignación regular:

va_list apcopy = ap; 

Es técnicamente no portátil y un comportamiento indefinido, pero funcionará con la mayoría de los compiladores y arquitecturas. En la convención de llamadas x86, va_list s son solo punteros en la pila y son seguros para copiar.

+1

- va_copy se define comúnmente como "va_copy #define (d, s) ((d) = (s)), por lo que podría ser mejor arrojarlo a un encabezado de 'portabilidad' protegido por un "#ifndef va_copy" –

+1

Esto se rompe en AMD64 con GCC. –

+1

@Alex B: luego use 'va_copy', que es compatible con GCC. Esta pregunta fue específicamente sobre Visual C++. –

5

Una cosa que puede hacer es si no lo contrario necesita la función vformat(), mover su aplicación en la función format() (no probado):

#include <stdarg.h> 
#include <string.h> 
#include <assert.h> 
#include <string> 
#include <vector> 


std::string format(const char *fmt, ...) 
{ 
    va_list ap; 

    enum {size = 1024}; 

    // if you want a buffer on the stack for the 99% of the time case 
    // for efficiency or whatever), I suggest something like 
    // STLSoft's auto_buffer<> template. 
    // 
    // http://www.synesis.com.au/software/stlsoft/doc-1.9/classstlsoft_1_1auto__buffer.html 
    // 
    std::vector<char> buf(size); 

    // 
    // where you get a proper vsnprintf() for MSVC is another problem 
    // maybe look at http://www.jhweiss.de/software/snprintf.html 
    // 

    // note that vsnprintf() might use the passed ap with the 
    // va_arg() macro. This would invalidate ap here, so we 
    // we va_end() it here, and have to redo the va_start() 
    // if we want to use it again. From the C standard: 
    // 
    //  The object ap may be passed as an argument to 
    //  another function; if that function invokes the 
    //  va_arg macro with parameter ap, the value of ap 
    //  in the calling function is indeterminate and 
    //  shall be passed to the va_end macro prior to 
    //  any further reference to ap. 
    // 
    // Thanks to Rob Kennedy for pointing that out. 
    // 
    va_start (ap, fmt); 
    int needed = vsnprintf (&buf[0], buf.size(), fmt, ap); 
    va_end(ap); 

    if (needed >= size) { 
     // vsnprintf reported that it wanted to write more characters 
     // than we allotted. So do a malloc of the right size and try again. 
     // This doesn't happen very often if we chose our initial size 
     // well. 
     buf.resize(needed + 1); 

     va_start (ap, fmt); 
     needed = vsnprintf (&buf[0], buf.size(), fmt, ap); 
     va_end(ap); 

     assert(needed < buf.size()); 
    } 

    return std::string(&buf[0]); 
} 
+0

Habría que llamar va_end y va_start de nuevo antes de la segunda llamada a vsnprintf, wouldn' usted? –

+0

Hmm - parece que tiene razón. Nunca me di cuenta de eso antes. - Reparado (creo). –

+0

@Zenikoder: 'auto_buffer <>' no es parte de la STL. –

8

Para Windows, puede simplemente definir va_copy mismo:

#define va_copy(dest, src) (dest = src) 
+0

Iría con esta solución si va_copy aún no está definido. MSVC solo lo define para 2013. Una asignación simple funcionará si la implementación es un puntero de pila, que es lo que hace msvc, pero causará problemas en gcc y clang con una arquitectura de 64 bits. Ver http://www.bailopan.net/blog/?p=30 –

1

va_copy() se admite directamente a partir de Visual Studio 2013. Entonces, si puedes confiar en que esté disponible, no necesitas hacer nada.

Cuestiones relacionadas