A previous question showed a nice way of printing to a string. The answer involved 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.
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_lists are just pointers into the stack and are safe to copy.
For Windows, you can simply define va_copy yourself:
#define va_copy(dest, src) (dest = src)
One thing you can do is if you do not otherwise need the vformat() function, move its implementation into the format() function (untested):
#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]);
}
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.
Related
Suppose a third-party library has a logging callback with a signature of:
using LogCallback = void (*)(const char* fmt, va_list ap);
and you need to provide a callback that passes the log message to a function that requires a std::string:
void PrintLogMessage(const std::string& message);
I assume you need to use one of the vsprintf family of functions:
std::string VaList2String(const char* fmt, va_list ap) {
/* ??? something with vnsprintf or vnsprintf_s ??? */
}
void MyLogCallback(const char* fmt, va_list ap) {
std::string message = VaList2String(format, ap);
PrintLogMessage(message);
}
What is the correct (portable and secure) way to implement VaList2String in the above that is compatible with all the major platforms/implementations?
void MyLogCallback(const char* fmt, va_list ap) {
std::string message;
va_list ap_copy;
va_copy(ap_copy, ap);
size_t len = vsnprintf(0, 0, fmt, ap_copy);
message.resize(len + 1); // need space for NUL
vsnprintf(&message[0], len + 1,fmt, ap);
message.resize(len); // remove the NUL
PrintLogMessage(message);
}
A va_list is commonly actually an array under the hood, so the ap being passed in as an argument is really a pointer. We need va_copy to make a copy of the pointed at va_list so we can traverse it twice.
Note that you could probably get away without the + 1 in the (first) resize and then would not need the second resize at all, but it would technically be undefined behavior as you're violating std::string's constraints by overwriting the terminal NUL that it maintains with another NUL.
sprintf_sis a Microsoft implementation of the function sprintf where they patched a flaw, adding an argument to take a boundary value where the function is limited to write.
An equivalent was introduced in C++11: snprintf. But here, we are talking of C++03 syntax.
Signatures:
count_char_written sprintf(char* string_out, const char* output_template, VARIADIC_ARGS);
// and
count_char_written sprintf_s(char* string_out, size_t buffer_max_size, const char* output_template, VARIADIC_ARGS);
Functionnaly, sprintf_s is more advanced than sprintf, because it avoids overflows.
But sprintf_s is Microsoft only!
What to do if you want to port back a C++03 code written with sprintf_s to POSIX compatible syntax?
Today both snprintf and vsnprintf should be available everywhere with the exception of Windows with MSVC12 and older. The simplest way for you is to provide snprintf/vsnprintf on Windows where it is not available.
Windows provides function _vsnprintf_s which is already similar to vsnprintf, but has following important differences with regards to what happens when provided buffer is too small:
Buffer content depends on the additional count argument which does not exist in vsnprintf. To get vsnprintf behavior you can pass _TRUNCATE here.
-1 is returned instead of number of characters required. This can be fixed by using _vscprintf function which only needs to be called if previous call to _vsnprintf_s has failed.
Additionally those functions do not support format specifiers added in C99 such as %zd. This cannot be easily resolved, you will have to avoid using them.
Code below:
int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
int r = -1;
if (size != 0)
{
va_list args_copy;
va_copy(args_copy, args);
r = _vsnprintf_s(buf, size, _TRUNCATE, fmt, args_copy);
va_end(args_copy);
}
if (r == -1)
{
r = _vscprintf(fmt, args);
}
return r;
}
int snprintf(char *buf, size_t size, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
int r = vsnprintf(buf, size, fmt, args);
va_end(args);
return r;
}
Note: Windows also provides _vsnprintf which looks better suited for this implementation, but it does not terminate the resulting string. If you want to use it, you should be careful.
#include <cstdarg>
using namespace std;
void do_something( va_list numbers, int count ) {
// ^
// Should I call this by reference here? I mean, va_list & numbers?
//... stuff
va_end( numbers );
}
void setList( int count, ... ) {
va_list numbers;
va_start( numbers, count );
do_something( numbers, count );
}
int main() {
setList( 2, 0, 1 );
return 0;
}
When taking va_list to another function, how should I pass it to that function? I know that va_end must be called when things are done with va_list I'm confused whether I should call it by reference or not. Will va_list be properly ended even though it is not called by reference?
Variable argument lists:
You should never use va_end in a function taking the va_list as argument!
From the (Linux) man-page for va_arg:
Each invocation of va_start() must be matched by a corresponding
invocation of va_end() in the same function.
(Emphasis mine)
In a function having a va_list argument, take the va_list by value (as C-library functions like vprintf, ... do, too).
Example:
#include <cstdarg>
#include <iostream>
void v_display_integers(int count, va_list ap) {
while(0 < count--) {
int i = va_arg(ap, int);
std::cout << i << '\n';
}
}
void display_integers(int count, ...) {
va_list ap;
va_start(ap, count);
v_display_integers(count, ap);
va_end(ap);
// You might use 'va_start' and 'va_end' a second time, here.
// See also 'va_copy'.
}
int main() {
display_integers(3, 0, 1, 2);
}
Note:
In C++ you should avoid variable argument lists. Alternatives are std::array, std::vector,std::initializer_list and variadic templates.
I prefer to call va_end in the same function in which I called va_start or va_copy, like any other resource allocation/deallocation pair. That style is required by the standard, although some implementations are more tolerant.
Normally, va_lists are passed by value. They are small objects.
The macro function va_end is implemented as either void statement or resetting the va_list variable.
http://research.microsoft.com/en-us/um/redmond/projects/invisible/include/stdarg.h.htm
Theoretically, you can call it anywhere, or even skip it. However, to be a good programmer, we need to put it after the va_ functions
Very frequently in my C++ code I use the following type of helper function:
static inline std::string stringf(const char *fmt, ...)
{
std::string ret;
// Deal with varargs
va_list args;
va_start(args, fmt);
// Resize our string based on the arguments
ret.resize(vsnprintf(0, 0, fmt, args));
// End the varargs and restart because vsnprintf mucked up our args
va_end(args);
va_start(args, fmt);
// Fill the string
if(!ret.empty())
{
vsnprintf(&ret.front(), ret.size() + 1, fmt, args);
}
// End of variadic section
va_end(args);
// Return the string
return ret;
}
It has a few upsides:
No arbitrary limits on string lengths
The string is generated in-place and doesn't get copied around (if RVO works as it should)
No surprises from the outside
Now, I have a few of problems with it:
Kind of ugly with the rescanning of the varargs
The fact that std::string is internally a contiguous string with space for a null terminator directly after it, does not seem to actually be explicitly stated in the spec. It's implied via the fact that ->c_str() has to be O(1) and return a null-terminated string, and I believe &(data()[0]) is supposed to equal &(*begin())
vsnprintf() is called twice, potentially doing expensive throw-away work the first time
Does anybody know a better way?
Are you married (pun intended) to std::sprintf()? If you are using C++ and the oh so modern std::string, why not take full advantage of new language features and use variadic templates to do a type safe sprintf that returns a std::string?
Check out this very nice looking implementation: https://github.com/c42f/tinyformat. I think this solves all your issues.
As you added the tag c++11 I suppose that you can use it. Then you can simplify your code to this:
namespace fmt {
template< class ...Args >
std::string sprintf( const char * f, Args && ...args ) {
int size = snprintf( nullptr, 0, f, args... );
std::string res;
res.resize( size );
snprintf( & res[ 0 ], size + 1, f, args... );
return res;
}
}
int main() {
cout << fmt::sprintf( "%s %d %.1f\n", "Hello", 42, 33.22 );
return 0;
}
http://ideone.com/kSnXKj
Don't do the first vsnprintf with size 0. Instead, use a stack buffer with some probable size (e.g. something between 64 to 4096), and copy that into the return value if it fits.
Your concerns about std::string's contiguity are misplaced. It is well-defined and perfectly okay to rely on.
Finally - I would like to reecho that a compile-time format-checking library would be better. You need C++14 for full power, but C++11 supports enough that you should be able to #ifdef some header code without losing any expressive. Remember to think about gettext though!
void printLine(const wchar_t* str, ...)
{
// have to do something to make it work
wchar_t buffer[2048];
_snwprintf(buffer, 2047, ????);
// work with buffer
}
printLine(L"%d", 123);
I tried
va_list vl;
va_start(vl,str);
and things like this but I didn't find a solution.
Here's a simple C code that does this, you will have to include stdarg.h for this to work.
void panic(const char *fmt, ...){
char buf[50];
va_list argptr; /* Set up the variable argument list here */
va_start(argptr, fmt); /* Start up variable arguments */
vsprintf(buf, fmt, argptr); /* print the variable arguments to buffer */
va_end(argptr); /* Signify end of processing of variable arguments */
fprintf(stderr, buf); /* print the message to stderr */
exit(-1);
}
The typical invocation would be
panic("The file %s was not found\n", file_name); /* assume file_name is "foobar" */
/* Output would be:
The file foobar was not found
*/
Hope this helps,
Best regards,
Tom.
What you want to use is vsprintf it accepts the va_list argument and there is sample
code on MSDN in the link.
EDIT:
You should consider _vsnprintf which will help avoid buffer overrun issues that vsprintf will happily create.
Typically one calls into a variable args version of the function, that accepts va_list. For example _snwprintf internally calls _vsnwprintf; try calling that.
Other people have already pointed you to the vprintf-family of functions, but this also (not surprisingly) is answered by the comp.lang.c FAQ, if you want to familiarize yourself with the other FAQ entries. (They're worth reading, IMO.)
How can I write a function that takes a format string and a variable number of arguments, like printf, and passes them to printf to do most of the work?