109

I'm unfortunate enough to be stuck using VS 2010 for a project, and noticed the following code still doesn't build using the non-standards compliant compiler:

#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    char buffer[512];

    snprintf(buffer, sizeof(buffer), "SomeString");

    return 0;
}

(fails compilation with the error: C3861: 'snprintf': identifier not found)

I remember this being the case way back with VS 2005 and am shocked to see it still hasn't been fixed.

Does any one know if Microsoft has any plans to move their standard C libraries into the year 2010?

Alex Jasmin
  • 39,094
  • 7
  • 77
  • 67
Andrew
  • 1,099
  • 2
  • 8
  • 3

6 Answers6

96

Short story: Microsoft has finally implemented snprintf in Visual Studio 2015. On earlier versions you can simulate it as below.


Long version:

Here is the expected behavior for snprintf:

int snprintf( char* buffer, std::size_t buf_size, const char* format, ... );

Writes at most buf_size - 1 characters to a buffer. The resulting character string will be terminated with a null character, unless buf_size is zero. If buf_size is zero, nothing is written and buffer may be a null pointer. The return value is the number of characters that would have been written assuming unlimited buf_size, not counting the terminating null character.

Releases prior to Visual Studio 2015 didn't have a conformant implementation. There are instead non-standard extensions such as _snprintf() (which doesn't write null-terminator on overflow) and _snprintf_s() (which can enforce null-termination, but returns -1 on overflow instead of the number of characters that would have been written).

Suggested fallback for VS 2005 and up:

#if defined(_MSC_VER) && _MSC_VER < 1900

#define snprintf c99_snprintf
#define vsnprintf c99_vsnprintf

__inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)
{
    int count = -1;

    if (size != 0)
        count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
    if (count == -1)
        count = _vscprintf(format, ap);

    return count;
}

__inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...)
{
    int count;
    va_list ap;

    va_start(ap, format);
    count = c99_vsnprintf(outBuf, size, format, ap);
    va_end(ap);

    return count;
}

#endif
nwellnhof
  • 32,319
  • 7
  • 89
  • 113
Valentin Milea
  • 3,186
  • 3
  • 28
  • 29
  • This will not always terminate the string with a 0 which is required on a overflow. The second if in c99_vsnprintf must be: if (count == -1) { if (size > 0) str[size-1] = 0; count = _vscprintf(format, ap); } – Lothar Feb 11 '14 at 02:09
  • 1
    @Lothar: The buffer is always null-terminated. According to MSDN: "if string truncation is enabled by passing _TRUNCATE, these functions will copy only as much of the string as will fit, leaving the destination buffer null-terminated, and return successfully". – Valentin Milea Feb 12 '14 at 09:18
  • should not you swap order of these two functions cause first calls seconds, so second must be first and first must be second? – Oleg Vazhnev May 24 '14 at 10:11
  • 3
    As of Jun 2014, there is still no "full" C99 support in Visual Studio, even with Update 2. [This blog](http://blogs.msdn.com/b/vcblog/archive/2013/07/19/c99-library-support-in-visual-studio-2013.aspx) gives the C99 support brief for MSVC 2013. As snprintf() family functions are now a part of C++11 standard, MSVC lags behind clang and gcc in C++11 implementation! – fnisi Jun 03 '14 at 23:24
  • 2
    With VS2014, C99 standards with snprintf and vsnprintf are added. See http://blogs.msdn.com/b/vcblog/archive/2014/06/18/crt-features-fixes-and-breaking-changes-in-visual-studio-14-ctp1.aspx. – vulcan raven Oct 29 '14 at 23:12
  • 1
    Mikael Lepistö: Really? For me _snprintf only works if I enable _CRT_SECURE_NO_WARNINGS. This work-around works fine without that step. – FvD Sep 10 '15 at 13:46
  • Spotted a related problem while fixing this in a couple of places - MS provide a vsnprintf() in VS2005 etc, but it isn't compatible! It's just a synonym for _vsnprintf() and returns -1 on truncation, not the full number of characters that would be required. As such it's like doing #define snprintf _snprintf, but worse because MS have essentially done the dodgy-define for you. So, the above code works fine but don't feel tempted to take a shortcut and use the MS version of vsnprintf() instead of the c99_vsnprintf() listed above! – Andy Krouwel Oct 19 '15 at 09:09
  • Have also just checked documentation, and the VS2015 version of vsnprintf() is still broken (it's included for 'ANSI compatibility', ironically). MS version - https://msdn.microsoft.com/en-us/library/1kt27hek.aspx, Normal version - http://www.cplusplus.com/reference/cstdio/vsnprintf/ – Andy Krouwel Oct 19 '15 at 09:15
  • It is illegal to reuse `va_list` argument like this. You should use `va_copy` for that. –  Jan 22 '19 at 21:51
  • I think StaceyGirl has a point. Is it really allowed to use ap twice in c99_vsnprintf()? Should it be copied before use? – Fabian Jul 03 '20 at 07:02
  • See also https://stackoverflow.com/questions/9937505/va-list-misbehavior-on-linux – Fabian Jul 03 '20 at 07:08
34

snprintf is not part of C89. It's standard only in C99. Microsoft has no plan supporting C99.

(But it's also standard in C++0x...!)

See other answers below for a workaround.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • 5
    It's not a good workaround, however...as there are differences in the snprintf and _snprintf behavior. _snprintf handles the null terminator retardedly when dealing with insufficient buffer space. – Andrew May 26 '10 at 18:37
  • Here's your citation -- http://connect.microsoft.com/VisualStudio/feedback/details/333273/request-for-c99-vla-in-visual-studio – James Sumners May 26 '10 at 18:41
  • 1
    @jsumners: Thanks :) @Andrew: That's why they've invented `sprintf_s`. :( – kennytm May 26 '10 at 18:45
  • 1
    An advantage of the C99 snprintf() over msvc _snprintf() and the snprintf() on older *nix platforms is that C99 snprintf() returns the number of characters that would have been written even if the buffer is too small. It's often useful if you don't know how much space to allocate in advance. – Alex Jasmin May 26 '10 at 19:13
  • 7
    @DeadMG - wrong. cl.exe supports the /Tc option, which instructs the compiler to compile a file as C code. Furthermore, MSVC ships with a version of standard C libraries. – Andrew May 26 '10 at 20:04
  • 2
    Just because the compiler technically can compile C code doesn't make it an actual C compiler, as evidenced by the fact that it's not been updated for the standard that arrived last decade. – Puppy May 26 '10 at 21:54
  • 3
    @DeadMG - it does, however, support the C90 standard as well as a few bits of C99, making it a C compiler. – Andrew May 27 '10 at 14:26
  • 15
    Only if you live between 1990 and 1999. – Puppy May 27 '10 at 17:41
  • 6
    -1, Microsoft's `_snprintf` is an unsafe function which behaves differently from `snprintf` (it doesn't necessarily add a null terminator), so the advice given in this answer is misleading and dangerous. – interjay Jun 04 '13 at 09:31
  • 2
    This is a comment, not an answer. Also, dead link. –  Nov 17 '15 at 05:20
8

If you don't need the return value, you could also just define snprintf as _snprintf_s

#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__)
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
3

I believe the Windows equivalent is sprintf_s

Il-Bhima
  • 10,744
  • 1
  • 47
  • 51
  • 7
    `sprintf_s` behaves differently from `snprintf`. – interjay Jun 04 '13 at 09:32
  • Specifically sprintf_s docs say, "If the buffer is too small for the text being printed then the buffer is set to an empty string". In contrast snprintf writes a truncated string to the output. – Andrew Bainbridge Oct 27 '14 at 20:04
  • 2
    @AndrewBainbridge -- you truncated the documentation. The full sentence is "If the buffer is too small for the text being printed then the buffer is set to an empty string and the invalid parameter handler is invoked." The default behavior for the invalid parameter handle is to terminate your program. If you want truncation with the _s family then you need to use snprintf_s and the _TRUNCATE flag. Yes, it is unfortunate that the _s functions do not give a convenient way of truncating. On the other hand, the _s functions do use template magic to infer buffer sizes, and that is excellent. – Bruce Dawson Nov 22 '14 at 00:23
2

Another safe replacement of snprintf() and vsnprintf() is provided by ffmpeg. You can checkout the source here (suggested).

1

I tried @Valentin Milea's code but I've got access violation errors. The only thing that worked for me was Insane Coding's implementation: http://asprintf.insanecoding.org/

Specifically, I was working with VC++2008 legacy code. From Insane Coding's implementation (can be downloaded from the link above), I used three files: asprintf.c, asprintf.h and vasprintf-msvc.c. Other files were for other versions of MSVC.

[EDIT] For completeness, their contents are as follows:

asprintf.h:

#ifndef INSANE_ASPRINTF_H
#define INSANE_ASPRINTF_H

#ifndef __cplusplus
#include <stdarg.h>
#else
#include <cstdarg>
extern "C"
{
#endif

#define insane_free(ptr) { free(ptr); ptr = 0; }

int vasprintf(char **strp, const char *fmt, va_list ap);
int asprintf(char **strp, const char *fmt, ...);

#ifdef __cplusplus
}
#endif

#endif

asprintf.c:

#include "asprintf.h"

int asprintf(char **strp, const char *fmt, ...)
{
  int r;
  va_list ap;
  va_start(ap, fmt);
  r = vasprintf(strp, fmt, ap);
  va_end(ap);
  return(r);
}

vasprintf-msvc.c:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include "asprintf.h"

int vasprintf(char **strp, const char *fmt, va_list ap)
{
  int r = -1, size = _vscprintf(fmt, ap);

  if ((size >= 0) && (size < INT_MAX))
  {
    *strp = (char *)malloc(size+1); //+1 for null
    if (*strp)
    {
      r = vsnprintf(*strp, size+1, fmt, ap);  //+1 for null
      if ((r < 0) || (r > size))
      {
        insane_free(*strp);
        r = -1;
      }
    }
  }
  else { *strp = 0; }

  return(r);
}

Usage (part of test.c provided by Insane Coding):

#include <stdio.h>
#include <stdlib.h>
#include "asprintf.h"

int main()
{
  char *s;
  if (asprintf(&s, "Hello, %d in hex padded to 8 digits is: %08x\n", 15, 15) != -1)
  {
    puts(s);
    insane_free(s);
  }
}