Best way to save va_list for later use in C / C ++

I use va_list to create a string that is displayed.

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

All this is fine and good, but now the user has the ability to change the language while the application is running. I need to recover all text strings and re-cache text bitmaps after initialization. I would like to save va_list and use it whenever text should be generated.

To give you a few more prerequisites, this should happen in the case where the key line that I am translating contains the dynamic part of the data.

 "Player Score:%d" 

This is the key line I need to translate. I would like to keep the number (s) indicated in va_list for later use (outside the scope of the function that initializes the text), if after initialization it must be re-translated. Preferably, I would like to keep a copy of va_list for use with vsnprintf.

I did some research on this and found several ways. Some of them I ask if it is a suitable method (in terms of stability and portability).

+7
c ++ c printf variadic-functions
source share
5 answers

Keeping va_list itself is not a great idea; the standard only requires the va_list argument to work with va_start() , va_arg() and va_end() . As far as I can tell, va_list not guaranteed to be copied.

But you do not need to store va_list . Copy the provided arguments into another data structure, such as a vector (void *, possibly), and get them later in the usual way. You need to be careful about types, but that always applies to printf functions in C ++.

+6
source share

This question really aroused my interest. Also, in my work I will run into a similar problem, so the solution developed here can also help me.

In short, I wrote an evidence-based concept code that caches variable arguments for later use - you can find it below.

I managed to get the code below to work correctly on both Windows and Linux based Linux. I compiled gcc on Linux and MSVC on Windows. There is a duplicate warning of abuse of va_start () from gcc - a warning that you can turn off in your file.

I would like to know if this code works in the Mac compiler. It may take a little tweaking to compile it.

I understand that this code:

  • Extreme in its abuse of va_start (), as defined by the ANSI C.
  • Old School Byte Oriented C.
  • Theoretically not portable when using the va_list variable as a pointer.

My use of malloc () and free () was very deliberate, as the va_list macros are C standard and not C ++ functions. I understand that your question title mentions C ++, but I tried to create a fully C-compatible solution, besides using some C ++ style comments.

This code also, without a doubt, has some errors or non-portability in processing format strings. I provide this as a proof of concept, which I hacked in two hours, and not a ready-made code sample, ready for professional use.

This disclaimer said: I hope you find the result as exciting as I am! It was a great hack question. The painful and twisted nature of the result gives me a deep laugh on my stomach .;)

 #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;

 }
+7
source share

You can use va_copy() , here is an example:

 va_list ap; va_list tmp; va_copy(tmp, ap); //do something with tmp va_end(tmp); 
+3
source share

What you are describing about "holding the number (s) provided in va_list" is a way to get closer to this.

va_list supports pointers to temporary memory on the stack (the so-called "automatic storage" in the C standard). After the function with args variables returned, this automatic storage disappeared and the contents are no longer usable. Because of this, you cannot just save a copy of va_list itself - the referenced memory will contain unpredictable content.

In the example below, you will need to store two integers that are reused when re-creating this message. Depending on how many different format strings you need to deal with, your approach may vary.

For a completely general type of approach you need to:

  • Write a function " cache_arguments() " that creates a dynamic memory buffer for the values ​​found in variable arguments.
  • This cache_arguments() will use the printf() -style format format string along with the macros va_start , va_arg , va_end . You will need to get the types according to printf() type specifiers, because sizeof(double) != sizeof(int) .
  • Store arguments in a memory cache with the same alignment and indentation expected by va_arg() on your platform. (Read the file varargs.h .)
  • Get your vsnprintf() calls working with this memory cache, instead of the pointer created by va_start() .

The above elements are possible on most platforms, including Linux and Windows.

An element that you might want to consider in translation is a problem with word order. What is written in English like:

Player Sam scored 20 points.

Is it possible to write in some (human) languages ​​only fluently with the help of a word order comparable to:

20 points were scored by player Sam.

For this reason, the Win32 API FormatMessage() uses a printf() -like format string with a functional difference that the parameters are enumerated with, for example:

Player% 1 scored% 2! d! points.
% 2! D! points were scored by player% 1.

(The string type is accepted for each argument, so %1 equivalent to %1!s! )

Of course, you cannot use the Win32 API, but the functionality of changing the word order of formatted arguments is what I am trying to present as a concept. You can also implement this in your software.

+2
source share

The way to do this in C is to send the argument structure of the function. You must pass the structure by reference and then copy (memcpy) the structure to a shared folder, which will allow you to reuse it later. You unravel the structure at the destination just like you sent it. You save the structure template for “setup and retrieve”.

0
source share

All Articles