Search code examples
c++printfposixc++03

How to properly replace sprintf_s by sprintf in C++03?


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?


Solution

  • 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.