我需要在va_list
上进行两次(或更多)次传球。我有一个大小的缓冲区,我想用sprintf写一个格式化的字符串。如果格式化的字符串不适合分配的空间,我想要将分配的空间加倍并重复直到它适合。
(作为旁注,我希望能够首先计算格式化字符串的长度并分配足够的空间,但我发现可以做到的唯一功能是_snprintf,并且在VS2005中不推荐使用。 ..)
现在,到目前为止还没有问题:我在每次调用前使用vsnprintf
并调用va_start
。
但我还创建了一个以va_list
为参数而不是“...”的函数。然后我再也不能使用va_start
了!我读过va_copy
,但VS2005不支持。
那么,你会怎么做?
答案 0 :(得分:6)
A previous question关于MSVC中缺少va_copy
有一些不错的建议,包括在MSVC中实现您自己的va_copy
版本:
#define va_copy(d,s) ((d) = (s))
您可能希望将其放入由#ifndef va_copy
和#ifdef _MSC_VER
保护的“可移植性”标头中,以便在VC上使用。
答案 1 :(得分:4)
va_copy()应该是C99规范的一部分;与所有可变参数支持一样,非常依赖于平台。 va_list,va_copy(),va_start(),va_end()宏在stdarg.h中定义。
GCC:当试图在GCC上重用va_list时,必须使用va_copy(),因为GCC实现会导致修改va_list,导致指针在被av ?? printf()使用后定位在最后一个param之后功能
SUN:尝试在SunStudio中重用va_list(v11,v12)时,va_list变量不受影响,可以根据需要重复使用多次,而无需使用va_copy()。
MS_Visual C:不确定,但看起来2010 VC ++文档确实提到'va_copy()'并且可能意味着重用va_list是合理的,但应该进行测试。
示例:
#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);
}
答案 2 :(得分:3)
迟到回复,但希望有人会觉得这很有用。 我需要使用va_copy但在vc2005中不可用,我搜索并发现自己在这个页面上。
我有点担心使用看似粗糙的东西:
va_copy(d,s) ((d) = (s))
所以我做了一点挖掘。我想看看vc_13是如何在vc2013中实现的,所以我编译了一个使用va_copy的测试程序并将其反汇编:
首先,根据msdn的用法:
void va_copy(
va_list dest,
va_list src
); // (ISO C99 and later)
在msvcr120中实现的va_copy的反汇编(全7行!):
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
正如您所看到的,它实际上就像在解析之前将src va_list的值分配给dest va_list一样简单。
由于我只使用ms编译器,并且目前使用vc2005可能会在将来升级,这就是我的用途:
#if _MSC_VER < 1800 // va_copy is available in vc2013 and onwards
#define va_copy(a,b) (a = b)
#endif
答案 3 :(得分:1)
我看到没有可移植的方式(我认为va_copy已经在C99中引入,因为没有可移植的方法来实现其在c89中的结果)。 va_list可以是声明为
的模拟引用类型typedef struct __va_list va_list[1];
(请参阅gmp了解该技巧的另一个用户)并解释了围绕它们的许多语言限制。顺便说一句,如果可移植性很重要,请不要忘记va_end。
如果可移植性不重要,我会检查stdard.h,看看我是否可以考虑真正的声明来破解。
答案 4 :(得分:0)
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());
// 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();
}