A previous question showed a nice way of printing to a string. The answer involved va_copy:
之前的一个问题展示了一种打印字符串的好方法。答案涉及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]);
}
}
}
The problem I'm having is that the above code doesn't port to Visual C++ because it doesn't provide va_copy (or even __va_copy). So, does anyone know how to safely port the above code? Presumably, I need to do a va_copy copy because vsnprintf destructively modifies the passed va_list.
我遇到的问题是上面的代码不支持Visual c++,因为它不提供va_copy(甚至是__va_copy)。那么,是否有人知道如何安全的移植上述代码?我可能需要做一个va_copy拷贝,因为vsnprintf会破坏性地修改传递的va_list。
4 个解决方案
#1
13
You should be able to get away with just doing a regular assignment:
你应该可以只做一个常规的作业:
va_list apcopy = ap;
It's technically non-portable and undefined behavior, but it will work with most compilers and architectures. In the x86 calling convention, va_list
s are just pointers into the stack and are safe to copy.
它在技术上是不可移植的和未定义的行为,但是它将与大多数编译器和体系结构一起工作。在x86调用约定中,va_lists只是堆栈中的指针,可以安全地复制。
#2
8
For Windows, you can simply define va_copy yourself:
对于Windows,您可以简单地定义va_copy自己:
#define va_copy(dest, src) (dest = src)
#3
5
One thing you can do is if you do not otherwise need the vformat()
function, move its implementation into the format()
function (untested):
您可以做的一件事是,如果您不需要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]);
}
#4
1
va_copy()
is directly supported starting in Visual Studio 2013. So if you can rely on that being available, you don't need to do anything.
在Visual Studio 2013中直接支持va_copy()。所以如果你可以依赖于可用性,你不需要做任何事情。
#1
13
You should be able to get away with just doing a regular assignment:
你应该可以只做一个常规的作业:
va_list apcopy = ap;
It's technically non-portable and undefined behavior, but it will work with most compilers and architectures. In the x86 calling convention, va_list
s are just pointers into the stack and are safe to copy.
它在技术上是不可移植的和未定义的行为,但是它将与大多数编译器和体系结构一起工作。在x86调用约定中,va_lists只是堆栈中的指针,可以安全地复制。
#2
8
For Windows, you can simply define va_copy yourself:
对于Windows,您可以简单地定义va_copy自己:
#define va_copy(dest, src) (dest = src)
#3
5
One thing you can do is if you do not otherwise need the vformat()
function, move its implementation into the format()
function (untested):
您可以做的一件事是,如果您不需要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]);
}
#4
1
va_copy()
is directly supported starting in Visual Studio 2013. So if you can rely on that being available, you don't need to do anything.
在Visual Studio 2013中直接支持va_copy()。所以如果你可以依赖于可用性,你不需要做任何事情。