3

I'm working with a C codebase in which my predecessor used:

#ifdef _MSC_VER
  // Map to equivalent function
  #define snprintf sprintf_s
#endif

The codebase needs to compile on Linux (gcc/clang), OSX (gcc/clang) and Windows (VS). I've attempted to compile the code in Visual Studio 2015 for the first time. Previously Visual Studio 2010 had been used. I encountered the error described here and was able to compile using a clue from the accepted answer:

#ifdef _MSC_VER
  #if _MSC_VER<1900
    #define snprintf sprintf_s
  #endif
#endif

The project compiles now with Visual Studio 2015 and clang on OSX. However, I'm concerned about a statement in the sprintf_s documentation:

Unlike snprintf, sprintf_s guarantees that the buffer will be null-terminated (unless the buffer size is zero).

If Visual Studio is including a C99 compliant version of snprintf, shouldn't the buffer be guaranteed to be null-terminated?

I wrote a small program to evaluate the behavior.

#include <stdio.h>
#include <string.h>

int main(int argc, char** argv) {
    char buf[5];
    snprintf(buf, sizeof(buf), "abcdef");
    printf("buffer: %s\n", buf);
    printf("size of buffer: %lu\n", strlen(buf));
    printf("last character a null terminator?: %s\n",
        (strcmp(&buf[4], "\0") == 0) ? "yes" : "no");
    return 0;
}

I built and ran the program on OSX, and Windows with Visual Studio 2015.

On OSX, the output is:

~$c99 main.c
~$./a.out 
buffer: abcd
size of buffer: 4
last character a null terminator?: yes

On Windows 7 with Visual Studio 2015 the output is

> printf_evaluation.exe
buffer: abcd
size of buffer: 4
last character a null terminator?: yes

Doesn't that demonstrate the the output is in fact null-terminated, and suggest that the MSDN documentation is incorrect? Is my example too trivial? Are there cases when the output of snprintf might not be NULL terminated with Visual Studio 2015?

Community
  • 1
  • 1
RjOllos
  • 2,900
  • 1
  • 19
  • 29
  • I think, they meant **Unlike _snprintf**, which indeed doesn't null-terminate. VS doesn't know snprintf at all (except as a macro for sprintf_s) – Ctx Jan 20 '16 at 23:08
  • I suspect it's a documentation bug rather than anything unusual/wrong with the implementation of `snprintf()` in visual studio. – P.P Jan 20 '16 at 23:08
  • 1
    Microsoft's 1980s implementation of `snprintf` was different to the C99 Standard one; that's the root of all these issues. IDK when (if ever) VS runtime would have been updated to use the C99 version. – M.M Jan 20 '16 at 23:09
  • MS have never made any effort to comply with C99 - what makes you think "Visual Studio is including a C99 compliant version of snprintf" ? – M.M Jan 20 '16 at 23:12
  • suggest, for verification of what actually happens.... write a small program that passes an 'overlenght' buffer to snprintf() then use the debugger to see what is actually placed in the resulting buffer. – user3629249 Jan 21 '16 at 22:23
  • Wow, your predecessor's macro is anything but "equivalent". Not only would it have been necessary to use `_snprintf_s()` instead of `sprintf_s()` to avoid an (artificial) crash, but the return value would have been wrong too (until VS2015, only `_scprintf()` unconditionally returned the "full" length). – Medinoc Apr 08 '17 at 07:37

1 Answers1

1

snprintf() would always nul terminate the buffer as long as the second argument to it is greater than zero. So yes, the MSDN documentation is wrong.

From C11 standard, snprintf():

The snprintf function is equivalent to fprintf, except that the output is written into an array (specified by argument s) rather than to a stream. If n is zero, nothing is written, and s may be a null pointer. Otherwise, output characters beyond the n-1st are discarded rather than being written to the array, and a null character is written at the end of the characters actually written into the array. If copying takes place between objects that overlap, the behavior is undefined.

(emphasis mine).

P.P
  • 117,907
  • 20
  • 175
  • 238
  • 3
    The MSDN documentation would be describing the behaviour of MS tools, which may differ from the ISO C standard. AFAIK , MS have never made any claim to support C99 (let alone C11) – M.M Jan 20 '16 at 23:12
  • See [this blog](http://blogs.msdn.com/b/vcblog/archive/2014/06/18/crt-features-fixes-and-breaking-changes-in-visual-studio-14-ctp1.aspx): snprintf and vsnprintf Are Now Implemented: The C99 snprintf and vsnprintf functions have been implemented. – RjOllos Jan 20 '16 at 23:14
  • @M.M [The latest visual studio provides nearly complete support for C99.](http://blogs.msdn.com/b/vcblog/archive/2014/06/18/crt-features-fixes-and-breaking-changes-in-visual-studio-14-ctp1.aspx) – P.P Jan 20 '16 at 23:15
  • 2
    I ran the example with MSVC 9.0 and 15.0. The earlier one does not have `snprintf` only `_snprintf`, using the latter both compilers produced code that wrote 5 characters to the string and did not terminate it. However MSVC 15.0 does have `snprintf` which behaves as OP says. MSVC documentation for [_snprintf](https://msdn.microsoft.com/en-us/library/2ts7cx93.aspx) says *"this function does not guarantee NULL termination"*, and that set of documentation does not mention `snprintf`. So yes, there is a documentation fault, and the two versions `snprintf` and `_snprintf` do not behave the same. – Weather Vane Jan 21 '16 at 00:00