问题
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?
回答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.
回答2:
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.
回答3:
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);
}
回答4:
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
回答5:
Hmmm, fell upon this question while trying to answer another one, and thought I would update it some some hard-won wisdom from the age of VS2015.
std::string and variadic templates
template<typename ... Args>
std::string format(const std::string& fmt, Args ... args)
{
size_t size = std::snprintf(nullptr, 0, fmt.c_str(), args ...) + 1; // Extra space for '\0'
char *cbuf = std::make_unique<char[]>(size).get();
std::snprintf(cbuf, size, fmt.c_str(), args ...);
return std::string(cbuf, cbuf + size - 1); // We don't want the '\0' inside our string tho
}
// usage:
::OutputDebugStringA(format("0x%012llx", size).c_str());
old skool revisited
// The macros defined in STDARG.H conform to the ISO C99 standard;
// the macros defined in VARARGS.H are deprecated but are retained for
// backward compatibility with code that was written before the ANSI C89
// standard.
#include <stdio.h>
#include <stdarg.h>
static void TraceA(LPCSTR format, ...) {
// (*) va_copy makes a copy of a list of arguments in its current state.
// The src parameter must already be initialized with va_start; it may
// have been updated with va_arg calls, but must not have been reset
// with va_end. The next argument that's retrieved by va_arg from dest
// is the same as the next argument that's retrieved from src.
// (*) The vsnprintf function returns the number of characters
// written, not counting the terminating null character. If the
// buffer size specified by count is not sufficiently large to
// contain the output specified by format and argptr, the return
// value of vsnprintf is the number of characters that would be
// written, not counting the null character, if count were
// sufficiently large. If the return value is greater than count
// - 1, the output has been truncated. A return value of -1
// indicates that an encoding error has occurred.
va_list args, argsCopy;
va_start(args, format);
va_copy(argsCopy, args);
// +1 for trailing \0 +1 for \n
size_t size = vsnprintf(nullptr, 0, format, args) + 1;
va_end(args);
auto buf = std::make_unique<char[]>(size);
size_t written = vsnprintf(buf.get(), size, format, argsCopy);
va_end(args);
// Do what you will with the result
char *buffer = buf.get();
}
来源:https://stackoverflow.com/questions/2288680/reuse-of-va-list