cvariadic-functions

How to call vsprintf multiple times on the same argument list?


I would like a function I can call this way:

    my_printf(int unimportant,
              "%-10s", "a string",
              "%5.1f", 1.23,
              format_string, value_to_format,
              /* possibly more pairs like this */
              (char *) NULL);

We may assume that the format strings contain at most one % escape sequence each.

I would like to use one of the sprintf family to format the values, perhaps producing a result like "a string , 1.2, …".

So far I know several ways that do not work. The following code shows (an incomplete sketch of) the basic idea:

      char * my_printf(int uninmportant, ...)
      {
         va_arg ap;
         va_start(ap, unimportant);
         
         while (1) {
           char *format = va_arg(ap, char *);
           if (!format) break;
           vsprintf(buffer, format, ap);
           /* append buffer somewhere */
         }
       }

This doesn't work because there is no guarantee about the usefulness of ap on return from vsprintf. It could be pointing anywhere or nowhere. The usual fix for that is to copy ap with va_copy before passing it. But that doesn't help in this case:

      char * my_printf(int uninmportant, ...)
      {
         va_arg ap;
         va_start(ap, unimportant);
         
         while (1) {
           char *format = va_arg(ap, char *);
           if (!format) break;
           va_arg ap_copy;
           va_copy(ap_copy, ap);
           vsprintf(buffer, format, ap_copy);
           /* append buffer somewhere */
           /* ?? seek ap forward to point to the next format string ?? */
         }
       }

Now vsprintf no longer destroys ap. But "seek ap forward to point to the next format string" seems quite difficult, because it seems to involve parsing and interpreting the contents of format, at least sufficiently well to know what type to pass to va_arg.

Similarly if I try to get around the problem by using sprintf, I still have to parse the format string in order to uunderstand how to call sprintf:

      char * my_printf(int uninmportant, ...)
      {
         va_arg ap;
         va_start(ap, unimportant);
         
         while (1) {
           char *format = va_arg(ap, char *);
           if (!format) break;
           ??TYPE?? arg = va_arg(ap, ??TYPE??);
           sprintf(buffer, format, arg);
           /* append buffer somewhere */
         }
       }

Again it seems the only way around this is to parse the value of format and then have a big switch with an arm for each possible type.

I can't have been the first or the thousandth person to want to do this. What is the conventional wisdom here?

  1. Here is what you have overlooked: …
  2. You are out of luck, it is well-known that there is no portable solution
  3. The only way to do this is to parse the format strings
  4. There is a dirty trick you can play with the preprocessor as follows: …
  5. The following code, published in 1982 in the Bell System Technical Journal, is helpful, and everyone uses some variation of it: …
  6. Your my_printf calling syntax is misconceived. Have it be called this other way instead: …
  7. (something else?)

I am aware that some compilers, such as GCC, provide nonportable extensions to assist with this, such as parse_printf_format, but I would like a more portable solution.

Thanks for any suggestions.


Solution

  • But "seek ap forward to point to the next format string" seems quite difficult, because it seems to involve parsing and interpreting the contents of format, at least sufficiently well to know what type to pass to va_arg.

    Yes, you cannot definedly advance through the elements of va_list without knowing (closely enough) the type of each element. This is a basic limitation of C variadic functions and the stdarg.h macros.

    it seems the only way around this is to parse the value of format and then have a big switch with an arm for each possible type.

    Yes, that's pretty much right. The job is a bit simplified because

    Ultimately, however, you need to apply knowledge of the types of the arguments passed in order to advance through the list. More than just argument size, this accounts for the possibility of altogether different argument-passing mechanisms, such as which registers, if any, arguments of different types might be passed in. There is no standard shortcut for this.