【问题标题】:va_copy -- porting to visual C++?va_copy -- 移植到 Visual C++?
【发布时间】:2023-12-05 10:15:01
【问题描述】:

A previous question 展示了一种打印到字符串的好方法。答案涉及 va_copy:

std::string format (const char *fmt, ...);
{
   va_list ap;
   va_start (ap, fmt);
   std::string buf = vformat (fmt, ap);
   va_end (ap);
   return buf;
}


std::string vformat (const char *fmt, va_list ap)
{
   // Allocate a buffer on the stack that's big enough for us almost
   // all the time.
   s ize_t size = 1024;
   char buf[size];

   // Try to vsnprintf into our buffer.
   va_list apcopy;
   va_copy (apcopy, ap);
   int needed = vsnprintf (&buf[0], size, fmt, ap);

   if (needed <= size) {
       // It fit fine the first time, we're done.
       return std::string (&buf[0]);
   } else {
       // vsnprintf reported that it wanted to write more characters
       // than we allotted.  So do a malloc of the right size and try again.
       // This doesn't happen very often if we chose our initial size
       // well.
       std::vector <char> buf;
       size = needed;
       buf.resize (size);
       needed = vsnprintf (&buf[0], size, fmt, apcopy);
       return std::string (&buf[0]);
   }

}

我遇到的问题是上面的代码没有移植到 Visual C++,因为它没有提供 va_copy(甚至 __va_copy)。那么,有谁知道如何安全地移植上述代码?据推测,我需要做一个 va_copy 复制,因为 vsnprintf 破坏性地修改了传递的 va_list。

【问题讨论】:

  • 我已经在 VC++ 中实现了类似的东西,并且从来不需要使用va_copy()。如果你不使用副本尝试它会发生什么?
  • 谁知道...它可能看起来有效。即使是这样,也并不意味着它是安全的。
  • 显然 va_copy() 是 C99 的东西。对于 VC++,您可以多次使用原始 va_list,而不必担心副本。 vsnprintf 不会尝试修改传递的列表。
  • 实际上,不使用 va_copy 复制它是可以的,因为 Visual C++ 中的 va_list 只是指向下一个参数的指针,调用 vsnprintf 不能修改调用者的值。但是,从技术上讲,第一次调用后 ap 的值是不确定的,所以你不应该在第二次调用时使用它。
  • 这里还值得一提的是,如果缓冲区不够大用于输出,Microsoft 的 vsnprintf 实现会返回 -1,但有一个例外:如果 NULL 不适合,它会给你返回填充的缓冲区和写入的字符数(显然,也许)没有NULL。 (我不确定如何通过 MS API 获得所需的缓冲区大小;eastl 只是迭代地寻找它。)

标签: c++ c visual-studio-2008 visual-c++


【解决方案1】:

你应该能够摆脱只做一个常规的任务:

va_list apcopy = ap;

它在技术上是不可移植和未定义的行为,但它适用于大多数编译器和架构。在 x86 调用约定中,va_lists 只是指向堆栈的指针,可以安全复制。

【讨论】:

  • True - va_copy 通常定义为“#define va_copy(d,s) ((d) = (s))”,因此最好将其放入“可移植性”标头中受“#ifndef va_copy”保护
  • 这在带有 GCC 的 AMD64 上中断。
  • @Alex B:然后使用 GCC 支持的va_copy。这个问题专门针对 Visual C++。
  • 是的,这是为了实现合理的可移植性。然而,这在 MSVC 上中断了,所以你又回到了 ifdefs。那好吧。 :(
  • 在 win32 和 xbox 上使用 MSVC,我既不知道也不需要 va_copy。我刚刚遇到了一个确实需要 va_copy 的工具链,所以很高兴你发布了这个问题。
【解决方案2】:

您可以做的一件事是,如果您不需要 vformat() 函数,请将其实现移至 format() 函数(未经测试):

#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include <string>
#include <vector>


std::string format(const char *fmt, ...)
{
   va_list ap;

   enum {size = 1024};

   // if you want a buffer on the stack for the 99% of the time case 
   //   for efficiency or whatever), I suggest something like
   //   STLSoft's auto_buffer<> template.
   //
   //   http://www.synesis.com.au/software/stlsoft/doc-1.9/classstlsoft_1_1auto__buffer.html
   //
   std::vector<char> buf( size);

   //
   // where you get a proper vsnprintf() for MSVC is another problem
   // maybe look at http://www.jhweiss.de/software/snprintf.html
   //

   // note that vsnprintf() might use the passed ap with the 
   //   va_arg() macro.  This would invalidate ap here, so we 
   //   we va_end() it here, and have to redo the va_start()
   //   if we want to use it again. From the C standard:
   //
   //       The object ap may be passed as an argument to
   //       another function; if that function invokes the 
   //       va_arg macro with parameter ap, the value of ap 
   //       in the calling function is indeterminate and 
   //       shall be passed to the va_end macro prior to 
   //       any further reference to ap.   
   //
   //    Thanks to Rob Kennedy for pointing that out.
   //
   va_start (ap, fmt);
   int needed = vsnprintf (&buf[0], buf.size(), fmt, ap);
   va_end( ap);

   if (needed >= size) {
       // vsnprintf reported that it wanted to write more characters
       // than we allotted.  So do a malloc of the right size and try again.
       // This doesn't happen very often if we chose our initial size
       // well.
       buf.resize( needed + 1);

       va_start (ap, fmt);
       needed = vsnprintf (&buf[0], buf.size(), fmt, ap);
       va_end( ap);

       assert( needed < buf.size());
   }

   return std::string( &buf[0]);
}

【讨论】:

  • 在第二次调用 vsnprintf 之前,您必须再次调用 va_end 和 va_start,不是吗?
  • 嗯 - 看起来你是对的。我以前从未意识到这一点。 -- 已修复(我认为)。
  • @Zenikoder: auto_buffer&lt;&gt; 不是 STL 的一部分。
【解决方案3】:

对于 Windows,您可以简单地自己定义 va_copy:

#define va_copy(dest, src) (dest = src)

【讨论】:

  • 如果 va_copy 尚未定义,我将采用此解决方案。 MSVC 只为 2013 年定义了它。如果实现是一个堆栈指针,那么一个简单的赋值就可以工作,这就是 msvc 所做的,但会导致 gcc 和 64 位架构的 clang 出现问题。见bailopan.net/blog/?p=30
【解决方案4】:

Visual Studio 2013 开始直接支持va_copy()。因此,如果您可以依赖它可用,则无需做任何事情。

【讨论】: