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.
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.
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!
I'm using VS2010 Pro compiler, when I build on x64 I get below compilation error. Compiles perfectly on x86.
error C2704: '' : __va_start intrinsic only allowed in varargs
Declaration of method:
int foo(char* buf, int maxChar, const char*& fmt);
definition:
int foo(char* buf, int maxChar, const char*& fmt)
{
int numChar = 0;
if (fmt)
{
va_list plist;
va_start(plist, fmt);
numChar = _vsnprintf(buf, maxChar, fmt, plist);
va_end(plist);
}
return numChar;
}
What is the meaning of the error? How to fix this?
I think it means pretty much what it says. The compiler won't allow you to use va_start, va_arg, etc, except in a variable argument function. Using va_start outside of a vararg function makes no sense.
This doesn't define a variable argument function:
int foo(char* buf, int maxChar, const char*& fmt)
This does:
int foo(char* buf, int maxChar, const char*& fmt, ...)
On x86, all arguments are passed on the stack, and it's semantically safe (albeit incorrect) to use va_start and friends to get "arguments".
However, on amd64 (and most likely on ARM), some arguments are passed via registers. In this case, using va_start in a function that isn't declared to take variable arguments is semantically unsafe - va_start would index into invalid memory.
You used a varargs macro in a function with a fixed number of arguments; MSDN link to the error.
In C/C++, there is a 'write() function which let me write to either file or a socket, I just pass in the file descriptor accordingly). And there is a fprintf() which allow me to do fprintf (myFile, "hello %d", name); but it only works for file.
Is there any api which allows me to do both?
i.e. able to let me do print formatting and able to switch between writing to file or socket?
Thank you.
Sure: just use fdopen on the socket to create a FILE* stream, and use fprintf appropriately.
In C, on POSIX-ish machines (which you must have to be using 'write()'), you can use:
fdopen() to create a file stream from a file descriptor.
fileno() to obtain a file descriptor from a file stream.
You need to be careful about flushing the file stream at appropriate times before using the matching file descriptor.
You can use sprintf or snprintf to print to a char * buffer, and then use write. To get a file descriptor from a FILE * variable, you can use fileno. There is no portable way to go from a file descriptor to a FILE *, though: you can portably to use fdopen to associate a FILE * with a valid file descriptor.
In addition, the latest POSIX standard specifies dprintf, but the GNU libc dprintf man page has this to say:
These functions are GNU extensions, not in C or POSIX. Clearly, the
names were badly chosen. Many systems (like MacOS) have incompatible
functions called dprintf(), usually some debugging version of printf(),
perhaps with a prototype like
void dprintf (int level, const char *format, ...);
where the first parameter is a debugging level (and output is to
stderr). Moreover, dprintf() (or DPRINTF) is also a popular macro name
for a debugging printf. So, probably, it is better to avoid this function in programs intended to be portable.
Of course, the libc manual page is not updated with the latest standard in mind, but you still have to be careful with using dprintf, since you might get something you don't want. :-)
You can do something like this, maybe :
#include <stdarg.h>
int fdprintf(int fd, const char* fmt, ...) {
char buffer[4096] = {0};
int cc;
va_list args;
va_start(args, fmt);
if ((cc = vsnprintf(buffer, 4096, fmt, args)) > 0) {
write(fd, buffer, cc);
}
va_end(args);
return cc;
}
generalizing tusbar answer, and to make it work with visual studio you can try the following codes:
`
int fdprintf(int fd, const char* fmt, ...) {
int cc;
va_list args;
va_start(args, fmt);
int len = _vscprintf(fmt,args) + 1;
char* buffer = new char[len];
buffer[len] = 0;
if ((cc = vsprintf_s(buffer, len-1, fmt, args)) > 0) {
write(fd, buffer, cc);
}
va_end(args);
delete[] buffer;
return cc;
}
`
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.