snprintf i Visual Studio 2010

102

Jestem na tyle niefortunny, że utknąłem przy użyciu VS 2010 dla projektu i zauważyłem, że następujący kod nadal nie jest kompilowany przy użyciu niezgodnego ze standardami kompilatora:

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

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

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

    return 0;
}

(kompilacja kończy się niepowodzeniem i pojawia się błąd: C3861: „snprintf”: nie znaleziono identyfikatora)

Pamiętam, że tak było w przypadku VS 2005 i jestem zszokowany, widząc, że nadal nie został naprawiony.

Czy ktoś wie, czy Microsoft planuje przenieść swoje standardowe biblioteki C na rok 2010?

Andrzej
źródło
1
... lub możesz po prostu zrobić „#define snprintf _snprintf”
Fernando Gonzalez Sanchez,
4
... mógłbyś, ale niestety _snprintf () to nie to samo co snprintf (), ponieważ nie gwarantuje zakończenia zerowania.
Andy Krouwel
Ok, więc będziesz musiał zresetować go do zera przed użyciem _snprintf (). Ja też się z tobą zgadzam. Rozwój pod MSVC jest okropny. Błędy są również mylące jak diabli.
Sowa

Odpowiedzi:

88

Krótka historia: Microsoft w końcu zaimplementował snprintf w Visual Studio 2015. We wcześniejszych wersjach można go zasymulować jak poniżej.


Długa wersja:

Oto oczekiwane zachowanie snprintf:

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

Zapisuje co najwyżej buf_size - 1znaków do bufora. Wynikowy ciąg znaków zostanie zakończony znakiem null, chyba że buf_sizewynosi zero. Jeśli buf_sizewynosi zero, nic nie jest zapisywane i buffermoże być pustym wskaźnikiem. Wartość zwracana to liczba znaków, które zostałyby zapisane przy założeniu nieograniczonej liczby buf_size, nie licząc kończącego znaku null.

Wersje wcześniejsze niż Visual Studio 2015 nie miały implementacji zgodnej. Istnieją zamiast tego niestandardowe rozszerzenia, takie jak _snprintf()(które nie zapisują terminatora null przy przepełnieniu) i _snprintf_s()(które mogą wymusić zakończenie zerowe, ale zwraca -1 przy przepełnieniu zamiast liczby znaków, które zostałyby zapisane).

Sugerowane rozwiązanie zastępcze dla VS 2005 i nowszych:

#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
Valentin Milea
źródło
To nie zawsze kończy ciąg z 0, który jest wymagany w przypadku przepełnienia. Drugim if w c99_vsnprintf musi być: if (count == -1) {if (size> 0) str [size-1] = 0; count = _vscprintf (format, ap); }
Lothar
1
@Lothar: Bufor jest zawsze zakończony wartością null. Zgodnie z MSDN: „jeśli obcinanie ciągu znaków jest włączone przez przekazanie _TRUNCATE, te funkcje skopiują tylko tyle ciągu, ile zmieszczą się, pozostawiając bufor docelowy zakończony wartością zerową i zwrócą pomyślnie”.
Valentin Milea,
2
Od czerwca 2014 r. Nadal nie ma „pełnej” obsługi C99 w programie Visual Studio, nawet w przypadku aktualizacji 2. Ten blog zawiera omówienie obsługi C99 dla MSVC 2013. Ponieważ funkcje rodziny snprintf () są teraz częścią standardu C ++ 11 , MSVC pozostaje w tyle za clang i gcc w implementacji C ++ 11!
fnisi
2
Wraz z VS2014 dodano standardy C99 z snprintf i vsnprintf. Zobacz blogs.msdn.com/b/vcblog/archive/2014/06/18/… .
Vulcan Raven
1
Mikael Lepistö: Naprawdę? Dla mnie _snprintf działa tylko wtedy, gdy włączę _CRT_SECURE_NO_WARNINGS. To obejście działa dobrze bez tego kroku.
FvD,
33

snprintfnie jest częścią C89. Jest standardem tylko w C99. Microsoft nie ma planu obsługującego C99 .

(Ale jest to również standard w C ++ 0x ...!)

Zobacz inne odpowiedzi poniżej, aby obejść ten problem.

kennytm
źródło
5
Nie jest to jednak dobre obejście ... ponieważ istnieją różnice w zachowaniu snprintf i _snprintf. _snprintf obsługuje terminator zerowy z opóźnieniem w przypadku niewystarczającej przestrzeni buforu.
Andrew
7
@DeadMG - źle. cl.exe obsługuje opcję / Tc, która instruuje kompilator, aby skompilował plik jako kod C. Ponadto MSVC jest dostarczany z wersją standardowych bibliotek C.
Andrew,
3
@DeadMG - obsługuje jednak standard C90, a także kilka bitów C99, dzięki czemu jest kompilatorem C.
Andrew,
15
Tylko jeśli mieszkasz między 1990 a 1999 rokiem.
Szczeniak
6
-1, Microsoft _snprintfjest niebezpieczną funkcją, która zachowuje się inaczej niż snprintf(niekoniecznie dodaje terminator zerowy), więc porada podana w tej odpowiedzi jest myląca i niebezpieczna.
interjay
8

Jeśli nie potrzebujesz zwracanej wartości, możesz również zdefiniować snprintf jako _snprintf_s

#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__)
Stefan Steiger
źródło
3

Uważam, że odpowiednikiem systemu Windows jest sprintf_s

Il-Bhima
źródło
7
sprintf_szachowuje się inaczej niż snprintf.
interjay
W szczególności dokumentacja sprintf_s mówi: „Jeśli bufor jest za mały dla drukowanego tekstu, wówczas bufor jest ustawiany na pusty ciąg”. Natomiast snprintf zapisuje obcięty ciąg na wyjściu.
Andrew Bainbridge,
2
@AndrewBainbridge - skróciłeś dokumentację. Całe zdanie brzmi: „Jeśli bufor jest zbyt mały dla drukowanego tekstu, wówczas bufor jest ustawiany na pusty łańcuch i wywoływana jest procedura obsługi nieprawidłowego parametru”. Domyślnym zachowaniem dla uchwytu nieprawidłowego parametru jest zakończenie programu. Jeśli chcesz obcinać z rodziną _s, musisz użyć snprintf_s i flagi _TRUNCATE. Tak, szkoda, że ​​funkcje _s nie dają wygodnego sposobu obcinania. Z drugiej strony funkcje _s używają magii szablonu do wnioskowania o rozmiarach buforów i to jest doskonałe.
Bruce Dawson
2

Kolejny bezpieczny zamiennik snprintf()i vsnprintf()jest zapewniany przez ffmpeg. Możesz sprawdzić źródło tutaj (sugerowane).

Marco Pracucci
źródło
1

Wypróbowałem kod @Valentin Milea, ale otrzymałem błędy związane z naruszeniem dostępu. Jedyną rzeczą, która działała dla mnie, była implementacja Insane Coding: http://asprintf.insanecoding.org/

W szczególności pracowałem ze starszym kodem VC ++ 2008. Od realizacji kodowania za Insane (można pobrać z linku powyżej), użyłem trzy pliki: asprintf.c, asprintf.hi vasprintf-msvc.c. Inne pliki były przeznaczone dla innych wersji MSVC.

[EDYTUJ] Aby uzyskać kompletność, ich zawartość jest następująca:

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);
}

Użycie (część test.cdostarczona przez 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);
  }
}
andertavares
źródło