重用va_list

Jon*_*tan 8 c printf visual-studio-2005

我需要做两次(或更多次)传球va_list.我有一个大小的缓冲区,我想用sprintf写一个格式化的字符串.如果格式化的字符串不适合分配的空间,我想要将分配的空间加倍并重复直到它适合.

(作为旁注,我希望能够首先计算格式化字符串的长度并分配足够的空间,但我发现可以做到的唯一功能是_snprintf,并且在VS2005中不推荐使用...)

现在,到目前为止没有问题:我在每次调用之前使用vsnprintfva_start调用.

但我还创建了一个函数,它将a va_list作为参数,而不是"......".然后我不能再使用va_start了!我已经读过了va_copy,但VS2005不支持它.

那么,你会怎么做?

Mic*_*urr 6

之前关于va_copyMSVC 缺乏的问题有一些不错的建议,包括实现你自己的版本va_copy用于MSVC:

#define va_copy(d,s) ((d) = (s))
Run Code Online (Sandbox Code Playgroud)

您可能希望将其转换为受 VC 保护的"可移植性"标头,#ifndef va_copy#ifdef _MSC_VER在VC上使用.


J J*_*son 6

va_copy() 应该是 C99 规范的一部分;与所有可变参数支持是非常平台相关的。va_list、va_copy()、va_start()、va_end() 宏在stdarg.h中定义。

GCC:当尝试在 GCC 上重用 va_list 时,必须使用 va_copy(),因为 GCC 实现导致 va_list 被修改,导致指针在 av??printf() 函数使用后定位在最后一个参数之后。

SUN:当尝试在 SunStudio (v11,v12) 中重用 va_list 时,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);
} 
Run Code Online (Sandbox Code Playgroud)


APr*_*mer 1

我发现没有可移植的方法(我认为 va_copy 已在 C99 中引入,因为在 c89 中没有可移植的方法来实现其结果)。va_list 可以是声明为的引用类型模型

typedef struct __va_list va_list[1];
Run Code Online (Sandbox Code Playgroud)

(请参阅 gmp 了解该技巧的另一个用户),这解释了它们周围的许多语言限制。顺便说一句,如果可移植性很重要,请不要忘记 va_end。

如果可移植性不重要,我会检查 stdard.h 并看看是否可以考虑真实的声明来破解某些内容。