2009-10-13 16 views
7

Estoy usando una lista_va para construir una cadena que se representa.La mejor manera de almacenar una lista_va para su uso posterior en C/C++

void Text2D::SetText(const char *szText, ...) 

Todo está bien, pero ahora el usuario tiene la capacidad de cambiar el idioma mientras se ejecuta la aplicación. Necesito regenerar todas las cadenas de texto y volver a almacenar en caché los mapas de bits de texto después de la inicialización. Me gustaría almacenar el va_list y usarlo siempre que el texto deba ser generado.

Para darle más información, esto tiene que suceder en el caso en que la cadena clave que estoy traduciendo tenga una parte dinámica de datos.

"Player Score:%d" 

Esa es la cadena clave que necesito traducir. Me gustaría retener los números provistos en la lista va_ para su uso posterior (fuera del alcance de la función que inicializa el texto) en el caso de que deba volver a traducirse después de la inicialización. De preferencia, me gustaría tener una copia de la lista va_ para usar con vsnprintf.

He hecho algunas investigaciones para hacer esto y he encontrado algunas maneras. Algunos de los cuales me pregunto si es un método apropiado (en términos de ser estable y portátil).

+2

¿Podría proporcionar una mejor descripción de lo que quiere decir con "más adelante"? Creo que debería ser muy claro para cualquiera que 'va_list' solo puede ser válido siempre que la invocación de la función variadic correspondiente esté todavía activa (es decir, desde el punto de vista dependiente de la implementación de bajo nivel, siempre que el marco de pila correspondiente con los parámetros está vivo). Cualquier intento de acceder a él después de que la función haya regresado es una receta para el desastre. – AnT

+1

use Boost.Format en su lugar para construir la cadena. No hay razón para explotar la seguridad del tipo si puede evitarlo. – jalf

+0

Sí, vamos a terminar haciendo algo similar a esto, porque este método (el que está en la pregunta) es simplemente defectuoso. – resolveaswontfix

Respuesta

6

Almacenar el va_list en sí no es una gran idea; el estándar solo requiere que el argumento va_list funcione con va_start(), va_arg() y va_end(). Por lo que puedo decir, no se garantiza que el va_list sea copiable.

Pero no necesita almacenar el va_list. Copie los argumentos suministrados en otra estructura de datos, como un vector (de void *, probablemente), y recupérelos más tarde de la forma habitual. Deberá tener cuidado con los tipos, pero ese siempre es el caso para las funciones de estilo printf en C++.

+0

Me he estado preguntando acerca de las cualidades de compilación/asignables de va_list. Parece que no puedo encontrar ninguna discusión al respecto en el estándar. –

+0

Eso sería porque es específico de la implementación, supongo. Conozco algunas CPU que almacenan los primeros argumentos en registros en lugar de una pila, lo que haría que la copia de va_list sea un poco más complicada. Además, va_list no sabe cuántos argumentos hay. Realmente necesita una estructura que tenga información cuantitativa, acceso aleatorio simple y semántica de copia bien definida. ¡Algunos idiomas pueden requerir los argumentos en un orden diferente! – Skizz

2

Lo que usted describe acerca de "mantener los números provistos en la lista va_" es la manera de abordar esto.

El va_list mantiene punteros a la memoria temporal en la pila (denominado "almacenamiento automático" en el estándar C). Después de que la función con variables args ha regresado, este almacenamiento automático se ha ido y los contenidos ya no se pueden usar. Debido a esto, no puede simplemente guardar una copia del va_list en sí mismo: la memoria a la que hace referencia contendrá contenido impredecible.

En el ejemplo que proporcione, deberá almacenar dos enteros que se vuelven a utilizar al volver a crear ese mensaje. Dependiendo de cuántas cadenas de formato diferentes tenga que manejar, su enfoque puede variar.

Para un tipo completamente general de enfoque, que se necesitan para:

  • escribir una función de "cache_arguments()" que crea un búfer de memoria dinámica de los valores encontrados en argumentos variables.
  • Este cache_arguments() usaría la cadena de formato de estilo printf(), junto con las macros va_start, , va_end. Tendrá que recuperar los tipos según los tipos-especificadores printf(), porque sizeof(double) != sizeof(int).
  • Almacene los argumentos en su memoria caché con la misma alineación y relleno esperados por va_arg() en su plataforma.(Lea su archivo varargs.h).
  • Obtenga sus llamadas a vsnprintf() trabajando con este búfer en memoria caché en lugar de un puntero creado por va_start().

Los elementos anteriores son todos posibles en la mayoría de las plataformas, incluidos Linux y Windows.

Un elemento que tal vez desee considerar sobre la traducción es la preocupación por el orden de las palabras. Lo que está escrito en inglés como:

Jugador Sam ha puntuado 20 puntos.

podría, en algunos lenguajes (humanos) sólo se puede escribir con fluidez con un análogo al orden de las palabras:

20 puntos han sido anotados por el jugador Sam.

Por esta razón, la API de Win32 FormatMessage() utiliza una cadena printf() -como formato, con la diferencia funcional que se numeran parámetros, como en:

jugador% 1 ha anotado 2% d! puntos.
% 2! D! puntos han sido anotados por el jugador% 1.

(El tipo de cadena se supone para cada argumento, por lo %1 es equivalente a %1!s!)

Por supuesto que no esté utilizando la API de Win32, pero la funcionalidad de alterar el orden de las palabras de los argumentos formateados es lo que intento introducir como concepto. Es posible que desee implementar esto en su software, también.

+0

Aparecerá un punto completamente válido al final, simplemente debería haber proporcionado un ejemplo con solo un% en la cadena fuente. También lo es el formato de un estándar va_list una vez que lo inicias para que yo pueda enviar un buffer que he creado (a través de cache_arguments) en vsprintf. Solo quiero asegurarme de que te estoy entendiendo correctamente. Esto necesita funcionar en Windows, Linux y Mac. Si no, tendré que hacer mi propia impresión que tome un vector de valores que almaceno en lugar de ... – resolveaswontfix

+0

La implementación de va_list no es estándar, pero el valor devuelto por va_arg() es. Para dimensionar la memoria requerida, realice una "ejecución de prueba" a través de los argumentos sin leer realmente sus valores. Use va_arg() para devolver sus direcciones. Tendrá que llamar a va_arg() un tiempo extra para recuperar un char final imaginario. Al restar el valor de retorno final de va_arg() de la inicial, puede obtener el número de bytes requeridos. Asigne ese número de bytes + sizeof (int). Haga una segunda pasada a través de los argumentos, ahora compítelos en el búfer asignado - comience en byte _sizeof (int) _ –

+0

Ah, sí ... debido a la forma en que va_start/va_arg necesita implementarse (devolviendo el contenido de un puntero)), será legal usar & va_arg (ap) en todas las plataformas, lo cual es necesario para obtener las direcciones de los parámetros. –

7

Esta pregunta realmente ha despertado mi interés. También enfrentaré un problema similar en mi propio trabajo, por lo que la solución ideada aquí también puede ayudarme.

En resumen, escribí un código de prueba de concepto que almacena en caché los argumentos variables para su uso posterior; puede encontrarlo a continuación.

Pude hacer que el siguiente código funcionara correctamente tanto en Windows como en Linux. Compilé con gcc en Linux y MSVC en Windows. Hay una advertencia repetida dos veces sobre abusar de va_start() de gcc, que advertencia podría deshabilitar en su archivo MAKE.

Me gustaría saber si este código funciona en un compilador de Mac. Puede llevar un pequeño retoque para compilarlo.

Soy consciente de que este código es:

  • extrema en su abuso de va_start() tal como se define en la norma ANSI C.
  • Orientado a bytes de la vieja escuela C.
  • Teóricamente no portátil en su uso de la variable va_list como puntero.

Mi uso de malloc() y free() fue muy deliberado, ya que las macros va_list son del estándar C y no son características C++.Me doy cuenta de que el título de su pregunta menciona C++, pero he intentado producir una solución completamente compatible con C, además de utilizar algunos comentarios al estilo de C++.

Este código también, sin duda, tiene algunos errores o no portabilidades en el procesamiento de cadenas de formato. Proporciono esto como una prueba de concepto que pirateé en dos horas, no como una muestra de código final lista para uso profesional.

Dicho descargo de responsabilidad, espero que encuentre el resultado tan delicioso como lo hice! Esta fue una pregunta encantadora para hackear. La naturaleza enfermiza y retorcida del resultado me da una risa profunda. ;)

 
#include <stdio.h> 
#include <stdarg.h> 
#include <stdlib.h> 
#include <string.h> 

#define VERBOSE 0 

#ifdef WINDOWS 
#define strdup _strdup 
#endif 

/* 
* struct cached_printf_args 
* 
* This is used as the pointer type of the dynamically allocated 
* memory which holds a copy of variable arguments. The struct 
* begins with a const char * which recieves a copy of the printf() 
* format string. 
* 
* The purpose of ending a struct with a zero-length array is to 
* allow the array name to be a symbol to the data which follows 
* that struct. In this case, additional memory will always be 
* allocted to actually contain the variable args, and cached_printf_args->args 
* will name the start address of that additional buffer space. 
* 
*/ 
struct cached_printf_args 
{ 
    const char * fmt; 
    char args[0]; 
}; 


/* 
* copy_va_args -- Accepts a printf() format string and va_list 
*     arguments. 
* 
*     Advances the va_list pointer in *p_arg_src in 
*     accord with the specification in the format string. 
* 
*     If arg_dest provided is not NULL, each argument 
*     is copied from *p_arg_src to arg_dest according 
*     to the format string. 
* 
*/ 
int copy_va_args(const char * fmt, va_list * p_arg_src, va_list arg_dest) 
{ 
    const char * pch = fmt; 

    int processing_format = 0; 

    while (*pch) 
    { 
     if (processing_format) 
     { 
      switch (*pch) 
      { 
      //case '!': Could be legal in some implementations such as FormatMessage() 
      case '0': 
      case '1': 
      case '2': 
      case '3': 
      case '4': 
      case '5': 
      case '6': 
      case '7': 
      case '8': 
      case '9': 
      case '.': 
      case '-': 

       // All the above characters are legal between the % and the type-specifier. 
       // As the have no effect for caching the arguments, here they are simply 
       // ignored. 
       break; 

      case 'l': 
      case 'I': 
      case 'h': 
       printf("Size prefixes not supported yet.\n"); 
       exit(1); 

      case 'c': 
      case 'C': 
       // the char was promoted to int when passed through '...' 
      case 'x': 
      case 'X': 
      case 'd': 
      case 'i': 
      case 'o': 
      case 'u': 
       if (arg_dest) 
       { 
        *((int *)arg_dest) = va_arg(*p_arg_src, int); 
        va_arg(arg_dest, int); 
       } 
       else 
        va_arg(*p_arg_src, int); 
#if VERBOSE 
       printf("va_arg(int), ap = %08X, &fmt = %08X\n", *p_arg_src, &fmt); 
#endif 
       processing_format = 0; 
       break; 

      case 's': 
      case 'S': 
      case 'n': 
      case 'p': 
       if (arg_dest) 
       { 
        *((char **)arg_dest) = va_arg(*p_arg_src, char *); 
        va_arg(arg_dest, char *); 
       } 
       else 
        va_arg(*p_arg_src, char *); 
#if VERBOSE 
       printf("va_arg(char *), ap = %08X, &fmt = %08X\n", *p_arg_src, &fmt); 
#endif 
       processing_format = 0; 
       break; 

      case 'e': 
      case 'E': 
      case 'f': 
      case 'F': 
      case 'g': 
      case 'G': 
      case 'a': 
      case 'A': 
       if (arg_dest) 
       { 
        *((double *)arg_dest) = va_arg(*p_arg_src, double); 
        va_arg(arg_dest, double); 
       } 
       else 
        va_arg(*p_arg_src, double); 
#if VERBOSE 
       printf("va_arg(double), ap = %08X, &fmt = %08X\n", *p_arg_src, &fmt); 
#endif 
       processing_format = 0; 
       break; 
      } 
     } 
     else if ('%' == *pch) 
     { 
      if (*(pch+1) == '%') 
       pch ++; 
      else 
       processing_format = 1; 
     } 
     pch ++; 
    } 

    return 0; 
} 

/* 
* printf_later -- Accepts a printf() format string and variable 
*     arguments. 
* 
*     Returns NULL or a pointer to a struct which can 
*     later be used with va_XXX() macros to retrieve 
*     the cached arguments. 
* 
*     Caller must free() the returned struct as well as 
*     the fmt member within it. 
* 
*/ 
struct cached_printf_args * printf_later(const char *fmt, ...) 
{ 
    struct cached_printf_args * cache; 
    va_list ap; 
    va_list ap_dest; 
    char * buf_begin, *buf_end; 
    int buf_len; 

    va_start(ap, fmt); 
#if VERBOSE 
    printf("va_start, ap = %08X, &fmt = %08X\n", ap, &fmt); 
#endif 

    buf_begin = (char *)ap; 

    // Make the 'copy' call with NULL destination. This advances 
    // the source point and allows us to calculate the required 
    // cache buffer size. 
    copy_va_args(fmt, &ap, NULL); 

    buf_end = (char *)ap; 

    va_end(ap); 

    // Calculate the bytes required just for the arguments: 
    buf_len = buf_end - buf_begin; 

    if (buf_len) 
    { 
     // Add in the "header" bytes which will be used to fake 
     // up the last non-variable argument. A pointer to a 
     // copy of the format string is needed anyway because 
     // unpacking the arguments later requires that we remember 
     // what type they are. 
     buf_len += sizeof(struct cached_printf_args); 

     cache = malloc(buf_len); 
     if (cache) 
     { 
      memset(cache, 0, buf_len); 
      va_start(ap, fmt); 
      va_start(ap_dest, cache->fmt); 

      // Actually copy the arguments from our stack to the buffer 
      copy_va_args(fmt, &ap, ap_dest); 

      va_end(ap); 
      va_end(ap_dest); 

      // Allocate a copy of the format string 
      cache->fmt = strdup(fmt); 

      // If failed to allocate the string, reverse allocations and 
      // pointers 
      if (!cache->fmt) 
      { 
       free(cache); 
       cache = NULL; 
      } 
     } 
    } 

    return cache; 
} 

/* 
* free_printf_cache - frees the cache and any dynamic members 
* 
*/ 
void free_printf_cache(struct cached_printf_args * cache) 
{ 
    if (cache) 
     free((char *)cache->fmt); 
    free(cache); 
} 

/* 
* print_from_cache -- calls vprintf() with arguments stored in the 
*      allocated argument cache 
* 
* 
* In order to compile on gcc, this function must be declared to 
* accept variable arguments. Otherwise, use of the va_start() 
* macro is not allowed. If additional arguments are passed to 
* this function, they will not be read. 
*/ 
int print_from_cache(struct cached_printf_args * cache, ...) 
{ 
    va_list arg; 

    va_start(arg, cache->fmt); 
    vprintf(cache->fmt, arg); 
    va_end(arg); 
} 

int main(int argc, char *argv) 
{ 
    struct cached_printf_args * cache; 

    // Allocates a cache of the variable arguments and copy of the format string. 
    cache = printf_later("All %d of these arguments will be %s fo%c later use, perhaps in %g seconds.", 10, "stored", 'r', 2.2); 

    // Demonstrate the time-line with some commentary to the output. 
    printf("This statement intervenes between creation of the cache and its journey to the display.\n" 

    // THIS is the call which actually displays the output from the cached printf. 
    print_from_cache(cache); 

    // Don't forget to return dynamic memory to the free store 
    free_printf_cache(cache); 

    return 0; 

} 
3

Puede utilizar va_copy(), aquí hay un ejemplo:

va_list ap; 
va_list tmp; 
va_copy(tmp, ap); 
//do something with tmp 
va_end(tmp); 
+1

La palabra clave en la pregunta era "uso posterior". Después de que la función retorna, no se pueden usar las variables tmp y ap. – kaspersky

0

La manera de hacerlo es en C para enviar una estructura de argumentos para la función en su lugar. Debería pasar la estructura por referencia y luego copiar (memcpy) la estructura en una ubicación común que le permitirá reutilizarla más adelante. Desentrañas la estructura en el destino de la misma manera que la enviaste. Mantiene la plantilla de la estructura para 'establecer y obtener'.

Cuestiones relacionadas