9

I need to do two (or more) passes over a va_list. I have a buffer of some size, and I want to write a formatted string with sprintf into it. If the formatted string doesn't fit in the allocated space I want to double the allocated space and repeat until it fits.

(As a side-note, i would like be able to calculate the length of the formatted string first and allocate enough space, but the only function that I found that can do that is _snprintf, and it is deprecated in VS2005 ...)

Now, so far there are no problems: i use vsnprintf and call va_start before each invokation.

But I've also created a function that takes a va_list as a parameter, instead of "...". Then I cannot use va_start again! I've read about va_copy, but it is not supported in VS2005.

So, how would you do this?

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
Jonatan
  • 3,752
  • 4
  • 36
  • 47

4 Answers4

6

The va_copy() is supposed to be part of the C99 specification; as with all variadic parameter support is very platform dependent. The va_list, va_copy(), va_start(), va_end() macros are defined in stdarg.h .

GCC: When attempting to reuse a va_list on GCC, one MUST use va_copy(), as the GCC implementation causes the va_list to be modified, causing the pointer to be positioned after last param after use by a v??printf() function.

SUN: When attempting to reuse a va_list in SunStudio (v11,v12), the va_list variable is untouched and can be reused as many times as desired without needing to va_copy().

MS_Visual C: Not certain, but looks like the 2010 VC++ docs do mention 'va_copy()' and may imply reuse of va_list is reasonable, but should be tested.

Example:

#include <stdio.h>
#include <stdarg.h>
/**
 * Version of vsprintf that dynamically resizes the given buffer to avoid overrun.
 * Uses va_copy() under GCC compile.
 **/    
int my_vsprintf(char **buffer, char *msg, va_list args)
{
   int bufLen = 0;
   va_list dupArgs;       // localize args copy

#ifdef __GNUC__
   va_copy(dupArgs,args); // Clone arguments for reuse by different call to vsprintf.
#else 
   dupArgs = args;        // Simply ptr copy for GCC compatibility
#endif

   // Perform 1st pass to calculate required buffer size.  The vsnprintf() funct
   // returns the number of chars (excluding \0 term) necessary to produce the output.
   // Notice the NULL pointer, and zero length.
   bufLen = vsnprintf(NULL,0,msg, dupArgs); 

   // NOTE: dupArgs on GCC platform is mangled by usage in v*printf() and cannot be reused.
#ifdef __GNUC__
   va_end(dupArgs); // cleanup 
#endif

   *buffer = realloc(*buffer,bufLen + 1);  // resize buffer, with \0 term included.

#ifdef __GNUC__
   va_copy(dupArgs,args); // Clone arguments for reused by different call to vsprintf.
#endif

   // Perform 2nd pass to populate buffer that is sufficient in size,
   // with \0 term size included.
   bufLen = vsnprintf(buffer, bufLen+1, msg, dupArgs);

   // NOTE: dupArgs on GCC platform is mangled by usage in v*printf() and cannot be reused.
#ifdef __GNUC__
   va_end(dupArgs); // cleanup
#endif

   return(bufLen);  // return chars written to buffer.
}

/**
 * Version of sprintf that dynamically resizes the given buffer to avoid buffer overrun
 * by simply calling my_vsprintf() with the va_list of arguments.
 *
 * USage:
 **/
int my_sprintf(char **buffer, char *msg, ...)
{
    int bufLen = 0;
    va_list myArgs; 

    va_start(myArgs, msg);   // Initialize myArgs to first variadic parameter.

    // re-use function that takes va_list of arguments.       
    bufLen = my_vsprintf(buffer, msg, myArgs ); 

    va_end(myArgs);
} 
J Jorgenson
  • 1,441
  • 12
  • 19
6

A previous question about the lack of va_copy in MSVC had some decent enough suggestions, including to implement your own version of va_copy for use in MSVC:

#define va_copy(d,s) ((d) = (s))

You might want to throw that into a 'portability' header protected by an #ifndef va_copy and #ifdef _MSC_VER for use on VC.

Community
  • 1
  • 1
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
3

Late to reply but hopefully someone will find this useful. I needed to use va_copy but being unavailable in vc2005, I searched and found myself on this page.

I was a little concerned by using the seemingly crude:

va_copy(d,s) ((d) = (s))

So I did a little digging around. I wanted to see how va_copy was implemented in vc2013, so I compiled a test program that uses va_copy and disassembled it:

First, its usage according to msdn:

void va_copy(
   va_list dest,
   va_list src
); // (ISO C99 and later)

disassembly of va_copy as implemented in msvcr120 (the whole 7 lines!):

PUSH EBP
MOV EBP,ESP
MOV EAX,DWORD PTR SS:[EBP+8]  ;get address of dest
MOV ECX,DWORD PTR SS:[EBP+0C] ;get address of src
MOV DWORD PTR DS:[EAX],ECX    ;move address of src to dest
POP EBP
RETN

So as you can see, it really is as simple as assigning the value of the src va_list to the dest va_list before parsing.

Since I only use ms compilers, and currently use vc2005 with the possibility of upgrading in the future, here's what I went with:

#if _MSC_VER < 1800 // va_copy is available in vc2013 and onwards
#define va_copy(a,b) (a = b)
#endif
ChaseTheSun
  • 3,810
  • 3
  • 18
  • 16
1

I see of no portable way (and I think that va_copy has been introduced in C99 because there was no portable way to achieve its result in c89). A va_list can be a reference type mock up declared as

typedef struct __va_list va_list[1];

(see gmp for another user of that trick) and that explains a lot of the language constraints around them. BTW, don't forget the va_end if portability is important.

If portability is not important, I'd check stdard.h and see if I can hack something considering the true declaration.

AProgrammer
  • 51,233
  • 8
  • 91
  • 143