va_copy -- porting to visual C++?

13,871

Solution 1

You should be able to get away with just doing a regular assignment:

va_list apcopy = ap;

It's technically non-portable and undefined behavior, but it will work with most compilers and architectures. In the x86 calling convention, va_lists are just pointers into the stack and are safe to copy.

Solution 2

For Windows, you can simply define va_copy yourself:

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

Solution 3

One thing you can do is if you do not otherwise need the vformat() function, move its implementation into the format() function (untested):

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

Solution 4

va_copy() is directly supported starting in Visual Studio 2013. So if you can rely on that being available, you don't need to do anything.

Share:
13,871
user48956
Author by

user48956

Updated on June 15, 2022

Comments

  • user48956
    user48956 almost 2 years

    A previous question showed a nice way of printing to a string. The answer involved va_copy:

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

    }

    The problem I'm having is that the above code doesn't port to Visual C++ because it doesn't provide va_copy (or even __va_copy). So, does anyone know how to safely port the above code? Presumably, I need to do a va_copy copy because vsnprintf destructively modifies the passed va_list.