重用 va_list

Posted

技术标签:

【中文标题】重用 va_list【英文标题】:Reuse of va_list 【发布时间】:2010-02-18 12:52:52 【问题描述】:

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

(作为旁注,我希望能够先计算格式化字符串的长度并分配足够的空间,但我发现唯一可以做到这一点的函数是 _snprintf,它在 VS2005 中已弃用。 ..)

现在,到目前为止没有任何问题:我使用vsnprintf 并在每次调用之前调用va_start

但我还创建了一个以va_list 作为参数的函数,而不是“...”。那我就不能再使用va_start了!我读过va_copy,但是VS2005不支持它。

那么,你会怎么做呢?

【问题讨论】:

【参考方案1】:

A previous question 关于 MSVC 中缺少 va_copy 有一些足够体面的建议,包括实现您自己的 va_copy 版本以在 MSVC 中使用:

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

您可能希望将其放入受#ifndef va_copy#ifdef _MSC_VER 保护的“可移植性”标头中,以便在VC 上使用。

【讨论】:

【参考方案2】:

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

GCC:当尝试在 GCC 上重用 va_list 时,必须使用 va_copy(),因为 GCC 实现会导致 va_list 被修改,导致指针在 v??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);
 

【讨论】:

【参考方案3】:

回复晚了,但希望有人会觉得这很有用。 我需要使用 va_copy 但在 vc2005 中不可用,我搜索并在此页面上找到了自己。

使用看似粗略的方式让我有点担心:

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

所以我做了一点挖掘。想看看在vc2013中va_copy是怎么实现的,所以编译了一个使用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

【讨论】:

【参考方案4】:

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

typedef struct __va_list va_list[1];

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

如果可移植性不重要,我会检查 stdard.h,看看我是否可以考虑到真正的声明来破解一些东西。

【讨论】:

好的。如果没有答案,那也是一个答案。谢谢。

以上是关于重用 va_list的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Django 中重用可重用的应用程序

如何使 UIPageViewController 像 tableview 重用单元一样重用控制器?

框架和设计模式的区别

如何使代码可重用? [关闭]

每个唯一的重用标识符是不是都有自己唯一的重用队列?

算法4:合并排序的套路 | 重用 | 分治