snprintf und Visual Studio 2010

102

Ich bin unglücklicherweise nicht in der Lage, VS 2010 für ein Projekt zu verwenden, und habe festgestellt, dass der folgende Code immer noch nicht mit dem nicht standardkonformen Compiler erstellt wird:

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

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

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

    return 0;
}

(Kompilierung fehlgeschlagen mit dem Fehler: C3861: 'snprintf': Bezeichner nicht gefunden)

Ich erinnere mich, dass dies vor langer Zeit bei VS 2005 der Fall war, und bin schockiert zu sehen, dass es immer noch nicht behoben wurde.

Weiß jemand, ob Microsoft Pläne hat, seine Standard-C-Bibliotheken in das Jahr 2010 zu verschieben?

Andrew
quelle
1
... oder Sie können einfach "#define snprintf _snprintf"
Fernando Gonzalez Sanchez
4
... Sie könnten, aber leider ist _snprintf () nicht dasselbe wie snprintf (), da es keine Nullbeendigung garantiert.
Andy Krouwel
Ok, Sie müssen es also auf Null setzen, bevor Sie _snprintf () verwenden können. Auch ich stimme dir zu. Die Entwicklung unter MSVC ist schrecklich. Die Fehler sind auch verdammt verwirrend.
Eule

Antworten:

88

Kurzgeschichte: Microsoft hat snprintf endlich in Visual Studio 2015 implementiert. In früheren Versionen können Sie es wie folgt simulieren.


Lange Version:

Hier ist das erwartete Verhalten für snprintf:

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

Schreibt höchstens buf_size - 1Zeichen in einen Puffer. Die resultierende Zeichenfolge wird mit einem Nullzeichen abgeschlossen, sofern sie nicht buf_sizeNull ist. Wenn buf_sizeNull ist, wird nichts geschrieben und bufferkann ein Nullzeiger sein. Der Rückgabewert ist die Anzahl der Zeichen, die unter der Annahme einer unbegrenzten Anzahl buf_sizeohne das abschließende Nullzeichen geschrieben worden wären .

Releases vor Visual Studio 2015 hatten keine konforme Implementierung. Es gibt stattdessen nicht standardmäßige Erweiterungen wie _snprintf()(die beim Überlauf keinen Nullterminator schreiben) und _snprintf_s()(die die Nullterminierung erzwingen können, beim Überlauf jedoch -1 anstelle der Anzahl der Zeichen zurückgeben, die geschrieben worden wären).

Vorgeschlagener Fallback für VS 2005 und höher:

#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
quelle
Dies beendet die Zeichenfolge nicht immer mit einer 0, die für einen Überlauf erforderlich ist. Das zweite if in c99_vsnprintf muss sein: if (count == -1) {if (size> 0) str [size-1] = 0; count = _vscprintf (format, ap); }
Lothar
1
@ Lothar: Der Puffer ist immer nullterminiert. Laut MSDN: "Wenn das Abschneiden von Zeichenfolgen durch Übergeben von _TRUNCATE aktiviert wird, kopieren diese Funktionen nur so viel von der Zeichenfolge, wie passen, lassen den Zielpuffer nullterminiert und kehren erfolgreich zurück."
Valentin Milea
2
Ab Juni 2014 gibt es in Visual Studio auch mit Update 2 noch keine "vollständige" C99-Unterstützung. In diesem Blog wird der C99-Support für MSVC 2013 beschrieben. Da die Funktionen der snprintf () -Familie jetzt Teil des C ++ 11-Standards sind , MSVC bleibt in der C ++ 11-Implementierung hinter clang und gcc zurück!
Fnisi
2
Mit VS2014 werden C99-Standards mit snprintf und vsnprintf hinzugefügt. Siehe blogs.msdn.com/b/vcblog/archive/2014/06/18/… .
Vulkanischer Rabe
1
Mikael Lepistö: Wirklich? Für mich funktioniert _snprintf nur, wenn ich _CRT_SECURE_NO_WARNINGS aktiviere. Diese Problemumgehung funktioniert ohne diesen Schritt einwandfrei.
FvD
33

snprintfist nicht Teil von C89. Es ist nur in C99 Standard. Microsoft hat keinen Plan, C99 zu unterstützen .

(Aber es ist auch Standard in C ++ 0x ...!)

Weitere Problemumgehungen finden Sie in den folgenden Antworten.

kennytm
quelle
5
Es ist jedoch keine gute Problemumgehung ... da es Unterschiede im Verhalten von snprintf und _snprintf gibt. _snprintf behandelt den Nullterminator verzögert, wenn nicht genügend Pufferplatz vorhanden ist.
Andrew
7
@DeadMG - falsch. cl.exe unterstützt die Option / Tc, mit der der Compiler angewiesen wird, eine Datei als C-Code zu kompilieren. Darüber hinaus wird MSVC mit einer Version der Standard-C-Bibliotheken ausgeliefert.
Andrew
3
@DeadMG - Es unterstützt jedoch den C90-Standard sowie einige Bits von C99, was es zu einem C-Compiler macht.
Andrew
15
Nur wenn Sie zwischen 1990 und 1999 leben.
Welpe
6
-1, Microsoft _snprintfist eine unsichere Funktion, die sich anders verhält snprintf(sie fügt nicht unbedingt einen Nullterminator hinzu), daher sind die in dieser Antwort gegebenen Ratschläge irreführend und gefährlich.
Interjay
8

Wenn Sie den Rückgabewert nicht benötigen, können Sie snprintf auch einfach als _snprintf_s definieren

#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__)
Stefan Steiger
quelle
3

Ich glaube das Windows-Äquivalent ist sprintf_s

Il-Bhima
quelle
7
sprintf_sverhält sich anders als snprintf.
Interjay
Insbesondere sprintf_s-Dokumente sagen: "Wenn der Puffer für den zu druckenden Text zu klein ist, wird der Puffer auf eine leere Zeichenfolge gesetzt." Im Gegensatz dazu schreibt snprintf eine abgeschnittene Zeichenfolge in die Ausgabe.
Andrew Bainbridge
2
@ AndrewBainbridge - Sie haben die Dokumentation abgeschnitten. Der vollständige Satz lautet: "Wenn der Puffer für den zu druckenden Text zu klein ist, wird der Puffer auf eine leere Zeichenfolge gesetzt und der ungültige Parameterhandler aufgerufen." Das Standardverhalten für das ungültige Parameterhandle ist das Beenden Ihres Programms. Wenn Sie die Familie _s abschneiden möchten, müssen Sie snprintf_s und das Flag _TRUNCATE verwenden. Ja, es ist bedauerlich, dass die Funktionen _s keine bequeme Möglichkeit zum Abschneiden bieten. Andererseits verwenden die _s-Funktionen Vorlagenmagie, um auf Puffergrößen zu schließen, und das ist ausgezeichnet.
Bruce Dawson
2

Ein weiterer sicherer Ersatz für snprintf()und vsnprintf()wird von ffmpeg bereitgestellt. Sie können die Quelle hier überprüfen (vorgeschlagen).

Marco Pracucci
quelle
1

Ich habe den Code von @Valentin Milea ausprobiert, aber ich habe Fehler bei der Zugriffsverletzung. Das einzige, was für mich funktioniert hat, war die Implementierung von Insane Coding: http://asprintf.insanecoding.org/

Insbesondere habe ich mit VC ++ 2008 Legacy-Code gearbeitet. Bei der Implementierung von Insane Coding (kann über den obigen Link heruntergeladen werden) habe ich drei Dateien verwendet : asprintf.c, asprintf.hund vasprintf-msvc.c. Andere Dateien waren für andere Versionen von MSVC.

[EDIT] Der Vollständigkeit halber sind ihre Inhalte wie folgt:

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

Verwendung (Teil test.cvon 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
quelle