I stumbled across a weird behaviour in MSVC (2013) recently which I wanted to clarify concerning variable arguments.
It appears having more than one parameter before the '...)' causes unexpected behaviour
int formatString(const char* msg, char* buffer, int bufferLength, ...)
{
int length = 0;
if (msg != nullptr) {
va_list args;
va_start(args, msg);
length = vsnprintf_s(buffer, bufferLength, bufferLength, msg, args);
va_end(args);
}
return length;
}
Calling this function like so
const char* message = "A word: %s and a number %d";
const int bufferLength = 1024;
char buffer[bufferLength];
int formattedMsgLen = formatString(message, buffer, bufferLength, "cheese", 4);
Will immediately cause the program to crash. If I memset the buffer to 0 before
memset(buffer, 0, 1024); // We know sizeof(char) == 1
This is written to the buffer:
"A word: A word: and a number 1024".
Which is completely wrong...
If however, I change the function to take a struct with the first three arguments combined
struct Message
{
const char* msg;
char* buffer;
int bufferLength;
};
int formatString(Message msg, ...)
{
int length = 0;
if (msg.msg != nullptr) {
va_list args;
va_start(args, msg);
length = vsnprintf_s(msg.buffer, msg.bufferLength, msg.bufferLength, msg.msg, args);
va_end(args);
}
return length;
}
When calling the new function like so:
const int bufferLength = 1024;
char buffer[bufferLength];
Message msg;
msg.buffer = buffer;
msg.msg = "A word: %s and a number %d";
msg.bufferLength = bufferLength;
int formattedMsgLen = formatString(msg, "cheese", 4);
This is written to the buffer:
"A word: cheese and a number 4"
Which is correct and this is what I would expect. Am I right in thinking you are only able to use vaargs with a function with only one parameter before? I don't think this is the case as the functions below have more than one first parameter
int fprintf(FILE *, const char *fmt, ...);
int sprintf(char *s, const char *fmt, ...);
Could this potentially be a bug in the compiler? However unlikely....
It probably isn't and I've done something stupid but I am definitely confused by what is happening. If anyone can shed some light on this I would be very grateful.
Thanks!
va_start() must be called with the name of the last parameter before the variable argument list:
int formatString(const char* msg, char* buffer, int bufferLength, ...)
{
// ...
va_list args;
// va_start(args, msg); <-- WRONG!
va_start(args, bufferLength);
// ...
}
Related
I have the following function that accepts a string and a list of arguments, the idea is that it works as a printf for the user. The function below is a minimal executable example of the function.
string description = "";
void error(string format, ...){
va_list arguments;
va_start(arguments, format);
va_list arg_copy;
va_copy(arg_copy, arguments);
const int32_t size = snprintf(nullptr, 0, format.c_str(), arguments) + 1;
const unique_ptr<char[]> buffer = make_unique<char[]>(size);
va_end(arguments);
snprintf(buffer.get(), size, format.c_str(), arg_copy);
va_end(arg_copy);
description += string(buffer.get(), buffer.get() + size - 1);
}
And I call it as follows.
int main()
{
int a = 123;
error("Format %d", a);
cout<< description;
return 0;
}
The expected output is: Format 123
The output result is (the number changes each execution): Format 378812424
I guess it is some problem with the memory but I am not able to discover where the problem is.
You are using the wrong print function. By using snprintf(), you are printing the address of the va_list itself, not the values that it refers to.
To print a va_list, you need to use vsnprintf() instead, eg:
string description = "";
void error(string format, ...){
va_list arguments;
va_start(arguments, format);
va_list arg_copy;
va_copy(arg_copy, arguments);
const int32_t size = vsnprintf(nullptr, 0, format.c_str(), arg_copy) + 1;
va_end(arg_copy);
vector<char> buffer(size);
vsnprintf(buffer.data(), size, format.c_str(), arguments);
va_end(arguments);
description.append(buffer.data(), size - 1);
}
I don't see a constructor for std::string that can consume a va_list. Is there a common solution for converting a va_list to a std::string?
I've seen solutions in the form of:
std::string vstring (const char * format, ...) {
std::string result;
va_list args;
va_start(args, format);
char buffer[1024];
vsnprintf(buffer, sizeof(buffer), format, args);
result = std::string(buffer);
va_end(args);
return result;
}
This feels error prone and hacky. Is there a way for std::string to be constructed from or operate on a va_list directly?
NOTE: The main problem I have with the solution above is the need to guess at the amount of memory I need. I don't want to waste too much or not have enough. Ideally, I would like a std::string style opaque allocation that just works.
NOTE: I need a solution that does not require third party library support.
vsnprintf() can calculate the needed buffer size without actually outputting to a buffer, so you usually don't need a separate char[] at all, you can just calculate the size, allocate the std::string to that size, and then use the std::string's own internal buffer for the output, eg:
std::string vstring (const char * format, ...)
{
std::string result;
va_list args, args_copy;
va_start(args, format);
va_copy(args_copy, args);
int len = vsnprintf(nullptr, 0, format, args);
if (len < 0) {
va_end(args_copy);
va_end(args);
throw std::runtime_error("vsnprintf error");
}
if (len > 0) {
result.resize(len);
// note: &result[0] is *guaranteed* only in C++11 and later
// to point to a buffer of contiguous memory with room for a
// null-terminator, but this "works" in earlier versions
// in *most* common implementations as well...
vsnprintf(&result[0], len+1, format, args_copy); // or result.data() in C++17 and later...
}
va_end(args_copy);
va_end(args);
return result;
}
Though, prior to C++11, using a separate buffer would be a more "correct" (ie, portable) and safer choice, eg:
std::string vstring (const char * format, ...)
{
std::string result;
va_list args, args_copy;
va_start(args, format);
va_copy(args_copy, args);
int len = vsnprintf(nullptr, 0, format, args);
if (len < 0) {
va_end(args_copy);
va_end(args);
throw std::runtime_error("vsnprintf error");
}
if (len > 0) {
std::vector<char> buffer(len+1);
vsnprintf(&buffer[0], buffer.size(), format, args_copy);
result = std::string(&buffer[0], len);
}
va_end(args_copy);
va_end(args);
return result;
}
You can use the fact that snprintf can be used with nullptr buffer and size 0 to get resulting buffer size and write the message into the std::string itself.
Note that va_copy should be used if you want to reuse va_list.
std::string vformat(const char *format, va_list args)
{
va_list copy;
va_copy(copy, args);
int len = std::vsnprintf(nullptr, 0, format, copy);
va_end(copy);
if (len >= 0) {
std::string s(std::size_t(len) + 1, '\0');
std::vsnprintf(&s[0], s.size(), format, args);
s.resize(len);
return s;
}
const auto err = errno;
const auto ec = std::error_code(err, std::generic_category());
throw std::system_error(ec);
}
std::string format(const char *format, ...)
{
va_list args;
va_start(args, format);
const auto s = vformat(format, args);
va_end(args);
return s;
}
I found a way to pass a variable length array in C++. But it fails 'wrap' function in below code. Actually I want to wrap format function in my project.
What am i doing wrong in my code?
test code
#include <iostream>
#include <stdarg.h>
#include <string>
void log(const char* format, ...)
{
va_list argptr;
va_start(argptr, format);
int length = _vscprintf(format, argptr);
char* buf_ = new char [length + 1];
int ret = vsnprintf(buf_, 1000, format, argptr);
if (ret >= 0) {
std::cout << buf_ << std::endl;
}
delete[] buf_;
va_end(argptr);
}
void wrap(const char *format, ...)
{
va_list ap;
va_start(ap, format);
log(format, ap);
va_end(ap);
}
int main()
{
log( "direct = %d", 1);
wrap("wrap = %d", 1);
return 0;
}
the result is here.
direct = 1
wrap = 15137088 // what's happen?
I found a way to pass a variable length array in C++
That isn't a variable-length array, and it isn't really idiomatic C++. The ... is a variable-length argument list, and is available in C.
The simplest reasonable way to wrap your log function is the variadic template one, which can simply be written as:
template <typename... Args>
void wrap(const char *format, Args&&... args) {
log(format, std::forward<Args>(args)...);
}
In the log function itself, vsnprintf returns the number of bytes that would have been written, in the event it fills the buffer. So, you can always just call it once with an optimistic buffer size, and grow the buffer if necessary: you don't need the non-standard _vscprintf. That would look something like:
void log(const char* format, ...)
{
va_list argptr;
va_start(argptr, format);
static const size_t DefaultSize = 200;
// pick some value that makes sense ^^ here
char buf[DefaultSize];
int rv = vsnprintf(buf, DefaultSize, format, argptr);
if (rv < 0) {
// we can't return errors with this prototype:
// should it throw?
return;
}
if (rv >= DefaultSize) {
vector<char> dynbuf(rv+1);
rv = vsnprintf(&dynbuf[0], dynbuf.size(), format, argptr);
std::cout << &dynbuf[0] << std::endl;
} else {
std::cout << buf << std::endl;
}
va_end(argptr);
}
Note also that wrap knows the types of all its arguments, but that information is discarded when you call the C-style variadic function log. You might consider Boost.Format as a type-safe alternative - as a bonus, it will manage the buffer for you.
Passing a va_list where a variable number of argumets (x, y, z) is expected isn't designed to work.
To achieve what you want, you need to do something like this:
void log_args(const char* format, va_list& argptr)
{
// I'm unsure on this... you may possibly need to make a separate
// copy of the va_list to pass in to each of _vscprintf and vsnprintf.
va_list second;
va_copy(second, argptr);
int length = _vscprintf(format, argptr);
char* buf_ = new char [length + 1];
int ret = vsnprintf(buf_, 1000, format, second);
if (ret >= 0) {
std::cout << buf_ << std::endl;
}
delete[] buf_;
}
void log(const char* format, ...)
{
va_list argptr;
va_start(argptr, format);
log_args(argptr);
va_end(argptr);
}
void wrap(const char *format, ...)
{
va_list ap;
va_start(ap, format);
log_args(format, ap);
va_end(ap);
}
In this example 'wrap' and 'log' appear the same... but I presume you want to do something additional in your real wrap function otherwise why would you be asking this question.
In c++11, you can use variadic templates
#include <iostream>
void tprintf(const char* format) // base function
{
std::cout << format;
}
template<typename T, typename... Targs>
void tprintf(const char* format, T value, Targs... Fargs) // recursive variadic function
{
for ( ; *format != '\0'; format++ ) {
if ( *format == '%' ) {
std::cout << value;
tprintf(format+1, Fargs...); // recursive call
return;
}
std::cout << *format;
}
}
int main()
{
tprintf("direct = %\n", 1);
tprintf("wrap = %\n", 1);
return 0;
}
I've written a custom print function. My problem is that I need to return a const char* as this has to be used in another function. I simply have no idea how to manage that...
anotherFunction (const char* text /*Here*/, unsigned __int32 value, unsigned __int64 bigVal);
I know the following example/s do/es not work as it should. That's what I've tried so far.
const char* CatchMessage (const char *message, ...)
{
va_list args;
va_start (args, message);
/*?*/
va_end (args);
return message;
}
I've yet only managed to get the correct output in cmd, but I actually need it as return value.
void CatchMessage (const char *message, ...)
{
va_list args;
va_start (args, message);
vfprintf (stdout, message, args);
va_end (args);
}
Call:
CatchMessage ("Some Input %s and %d equals to %d", randString, randNumber, secRandNumber);
Should return:
"Some Input stuff and 12 equals to 6"
I have not been able to find a solution. Any help would be appreciated.
Q: How do I get this CatchMessage function to return the correctly formatted const char* ?
It sounds like CatchMessage should take a pointer to a char buffer (and its size), and vsnprintf() into that buffer.
Since you're using C++ (at least according to the tags on the question) why not just return the string in a std::string ?
The problem with returning a (const) char * is that you have to have a buffer somewhere.
There are several ways to accomplish this:
The caller has to provide that buffer
You have to malloc() it
The function itself as a static buffer, but that would make it non-reentrant - which would be bad for multithreading etc.
Ad 1:
void CatchMessage(char * result, size_t maxlen, const char *message, ...)
{
va_list ap;
va_start(ap, message);
vsnprintf(result, maxlen, message, ap);
va_end(ap);
}
called with
char buffer[500];
CatchMessage(buffer, sizeof buffer, "Some Input %s and %d equals to %d", randString, randNumber, secRandNumber);
anotherfunction(buffer, ...)
ad 2:
char * CatchMessage(const char *message, ...)
{
size_t size = 500;
char * result = malloc(size);
if (!result) return NULL; // error handling!
while (1) {
va_list ap;
va_start(ap, message);
size_t used = vsnprintf(result, size, message, ap);
va_end(ap);
char * newptr = realloc(result, size);
if (!newptr) { // error
free(result);
return NULL;
}
result = newptr;
if (used <= size) break;
size = used;
}
return result;
}
called with
char * buffer = CatchMessage(buffer, sizeof buffer, "Some Input %s and %d equals to %d", randString, randNumber, secRandNumber);
if (!buffer) { /* error handling: no memory! */ }
anotherfunction(buffer, ...)
free(buffer); // important for avoiding memory leaks
ad 3:
char * CatchMessage(const char *message, ...)
{
static char result[500]; // static is important here! Otherwise the memory will be freed immediately after returning.
va_list ap;
va_start(ap, message);
vsnprintf(result, sizeof result, message, ap);
va_end(ap);
return result;
}
called with
char * buffer = CatchMessage(buffer, sizeof buffer, "Some Input %s and %d equals to %d", randString, randNumber, secRandNumber);
anotherfunction(buffer, ...)
There is no other option, especially not defining
char result[500];
in the function and then returning it: this array lives on the stack and is freed immediately after return. It cannot be safely accessed by the caller; its contents are just undefined.
If you do not care abour re-entrancy, you could return pointer to static buffer:
#define MESSAGE_MAX 1024
const char *
CatchMessage (const char *message, ...)
{
static buffer[MESSAGE_MAX];
va_list args;
va_start (args, message);
vsnprintf (buffer, MESSAGE_MAX, message, args);
va_end (args);
return buffer;
}
Notes:
This implementation is not thread-safe. If you care about thread safety, use thread local storage instead of static buffer
This implementation has hardcoded upper limit on message length. If that's not desirable, and your compiler is C99 compliant, you could call first vsprintf with NULL as first argument to know result string length, then allocate buffer of that side.
In C++, the use of variadic arguments is bad because there is a certain emphasis on type safety (and memory safety). So, you may want to propose a version that is actually type safe (and yes, that is possible), and yet proposes a similar interface:
template <typename H, typename... Args>
void format(std::ostream& out,
char const* format,
size_t len,
H const& head,
Args const&... args);
// Variations with 'char const (&)[N]' and 'std::string' formats
// as well as variations returning directly a 'std::string'.
Now, the implementation of format is not too difficult; especially without support for positional arguments. A simplistic one can be found below:
inline void format_string(std::ostream& out, char const* const* c) { out << *c; }
inline void format_string(std::ostream& out, std::string const* s) { out << *s; }
inline void format_string(std::ostream& out, void const*); // will throw
template <typename Integral, typename = enable_integral<Integral>::type>
inline void format_integral(std::ostream& out, Integral const* i) { out << *i; }
inline void format_integral(std::ostream& out, void const*); // will throw
inline size_t format_consume(std::ostream& out,
char const* const format,
size_t const length)
{
char const* end = format + length;
char const* current = format;
do {
// 1. Find first "format identifier", output stuff in-between
char const* perc = std::find(current, end, '%');
if (perc != current) { out.write(current, perc - current); }
current = perc;
// 2. %% is % escaped by %, so output it directly
while (*current == '%' and *(current + 1) == '%') {
out.put('%');
current += 2;
}
} while (current != end and *current != '%');
// 3. Return number of characters of format parameter consumed
return current - format;
} // format_consume
inline void format(std::ostream& out, char const* format, size_t len) {
size_t const consumed = format_consume(out, format, len);
if (consumed != len) { throw std::runtime_exception("Missing arguments"); }
} // format
template <typename H, typename typename... Args>
void format(std::ostream& out,
char const* format,
size_t len,
H const& head,
Args const&... args)
{
size_t const consumed = format_consume(out, format, len);
if (consumed == len) { throw std::runtime_exception("Extraneous arguments"); }
format += consumed;
len -= consumed;
assert(*format == '%');
switch(*(format+1)) {
case 's': format_string(out, &head); break;
case 'd': format_integral(out, &head); break;
default: throw std::runtime_exception("Invalid specifier");
}
format(out, format+2, len-2, args...);
} // format
In the general case, it is slightly more hairy, as you need to parse the modifiers, etc... but then, for a production-ready implementation, I advise a look at Boost.Format.
char* CatchMessage (size_t size, const char *message, ...)
{
char result[size];
va_list args;
va_start (args, message);
vsprintf (result, message, args);
va_end (args);
return result;
}
See this for reference.
And for glglgl: If you want to use the clean way, use vsnprintf(result, size, message, args)
I am porting an application on Mac OS X which was written for Windows.
In this application, there are many instances of _vscwprintf and _vscprintf.
This question helped me to implement _vsprintf on Mac OS X. But same technique for _vswprintf is not working.
Can anyone give the alternative of _vscwprintf on Mac OS X? Or there any equivalent method for this?
Microsoft describes the functions as returning the number of characters that would be used if the string were formatted — note that they are documented as not including the null terminator.
int _vscprintf(
const char *format,
va_list argptr
);
int _vscwprintf(
const wchar_t *format,
va_list argptr
);
Initial answer
These functions can, therefore, be emulated with vsprintf() and vswprintf():
int _vscprintf(const char *format, va_list argptr)
{
return(vsnprintf(0, 0, format, argptr));
}
int _vscwprintf(const wchar_t *format, va_list argptr)
{
return(vswprintf(0, 0, format, argptr));
}
It is up to you whether you remove the leading underscore; I would.
Note that the _vscwprintf() implementation above is flawed; see the code below.
vscprintf() and scprintf()
Apologies: I wrote vsprintf() where I needed to write vsnprintf() (now fixed in the code above); however, vswprintf() already has the safer interface with the buffer length, so there is no vsnwprintf(). There's a reason I prefer to test compile code before (or shortly after) posting it — it's been irksome not having the wherewithal to do so for a couple of days.
Here's an SSCCE for vscprintf() (and scprintf()):
#include <stdio.h>
#include <stdarg.h>
extern int vscprintf(const char *format, va_list argptr);
extern int scprintf(const char *format, ...);
int vscprintf(const char *format, va_list argptr)
{
return(vsnprintf(0, 0, format, argptr));
}
int scprintf(const char *format, ...)
{
va_list args;
va_start(args, format);
int rc = vscprintf(format, args);
va_end(args);
return rc;
}
int main(void)
{
int l = scprintf("%-8s %8d\n", "abc", 123);
if (l > 0)
{
char buffer[l+1];
int n = snprintf(buffer, sizeof(buffer), "%-8s %8d\n", "abc", 123);
printf("%d = %d: %s", l, n, buffer);
}
return 0;
}
Output:
18 = 18: abc 123
vscwprintf() and scwprintf()
It turns out to be harder to simulate _vscwprintf() because the vswprintf() function is not as helpful as the vsnprintf() function. Specifically, vswprintf() reports an error if the formatted string won't fit in the formatted space, whereas vsnprintf() reports the number of characters that would have been needed in the buffer if it was going to fit. Hence, you have to work by trial and error:
#include <stdio.h>
#include <stdarg.h>
#include <wchar.h>
extern int vscwprintf(const wchar_t *format, va_list argptr);
extern int scwprintf(const wchar_t *format, ...);
int vscwprintf(const wchar_t *format, va_list argptr)
{
// Unlike vsnprintf(), vswprintf() does not tell you how many
// characters would have been written if there was space enough in
// the buffer - it just reports an error when there is not enough
// space. Assume a moderately large machine so kilobytes of wchar_t
// on the stack is not a problem.
int buf_size = 1024;
while (buf_size < 1024 * 1024)
{
va_list args;
va_copy(args, argptr);
wchar_t buffer[buf_size];
int fmt_size = vswprintf(buffer, sizeof(buffer)/sizeof(buffer[0]), format, args);
if (fmt_size >= 0)
return fmt_size;
buf_size *= 2;
}
return -1;
}
int scwprintf(const wchar_t *format, ...)
{
va_list args;
va_start(args, format);
int rc = vscwprintf(format, args);
va_end(args);
return rc;
}
int main(void)
{
int l = scwprintf(L"%-8ls %8d\n", L"abc", 123);
if (l > 0)
{
wchar_t buffer[l+1];
int n = swprintf(buffer, sizeof(buffer)/sizeof(buffer[0]), L"%-8ls %8d\n", L"abc", 123);
wprintf(L"%d = %d: %ls", l, n, buffer);
}
return 0;
}
When run, this produces the output
18 = 18: abc 123
(the same as before).
Tested on Mac OS X 10.8.3 using GCC 4.7.3 (which was built on Mac OS X 10.7.5, but that shouldn't cause any problems).
I recommend using open_wmemstream instead. Something like that:
#include <stdio.h>
#include <string>
#include <stdarg.h>
using namespace std;
wstring wstring_format(const wchar_t* format, ...)
{
wchar_t* buf;
size_t size;
FILE* stream = open_wmemstream(&buf, &size);
va_list args;
va_start(args, format);
vfwprintf(stream, format, args);
va_end(args);
fclose(stream);
wstring result(buf, buf + size);
free(buf);
return result;
}