Wird snprintf () IMMER null beendet?

82

Ist snprintf immer null, um den Zielpuffer zu beenden?

Mit anderen Worten, ist dies ausreichend:

char dst[10];

snprintf(dst, sizeof (dst), "blah %s", somestr);

oder musst du das so machen, wenn somestr lang genug ist?

char dst[10];

somestr[sizeof (dst) - 1] = '\0';
snprintf(dst, sizeof (dst) - 1, "blah %s", somestr);

Ich interessiere mich sowohl für das, was der Standard sagt, als auch für das, was eine beliebte libc tun könnte, was kein Standardverhalten ist.

Prof. Falken
quelle
Wollen Sie im zweiten Beispiel nicht irgendwo oder dst beenden?
Hudson
@chux, Martin Ba hat das in der akzeptierten Antwort behandelt. :)
Prof. Falken
@chux Ich denke es war gut, dein Kommentar hat nur sehr deutlich gemacht, dass wenn dest i 0 lang ist, nichts geschrieben wird. Ich nehme jeden Kommentar als mögliche Ausrede, um mit anderen Stapelüberblumen zu plaudern. :)
Prof. Falken
@Prof. Falken Ich stimme zu, dass der Kommentar in Ordnung und explizit war, aber mit den Antworten überflüssig - habe das in meiner Bewertung nur verpasst.
chux - Wiedereinsetzung Monica
stackoverflow.com/a/8712996/193892 Visual Studio unterstützt jetzt snprintf ()
Prof. Falken

Antworten:

71

Wie die anderen Antworten ergeben: Es sollte :

snprintf... Schreibt die Ergebnisse in einen Zeichenfolgenpuffer. (...) wird mit einem Nullzeichen abgeschlossen, es sei denn, buf_size ist Null.

Sie müssen also nur darauf achten, dass Sie keinen Puffer mit der Größe Null an ihn übergeben, da er (offensichtlich) keine Null ins "Nirgendwo" schreiben kann.


Doch Vorsicht , dass Microsoft die Bibliothek nicht hat eine Funktion namens snprintfsondern historisch nur hatte eine Funktion namens _snprintf(Anmerkung führende Unterstrich) , die nicht nicht anhängen eine abschließende Null. Hier sind die Dokumente (VS 2012, ~~ VS 2013):

http://msdn.microsoft.com/en-us/library/2ts7cx93%28v=vs.110%29.aspx

Rückgabewert

Es sei len die Länge der formatierten Datenzeichenfolge (ohne die abschließende Null). len und count sind in Bytes für _snprintf, breite Zeichen für _snwprintf.

  • Wenn len <count, werden len-Zeichen im Puffer gespeichert, ein Nullterminator wird angehängt und len wird zurückgegeben.

  • Wenn len = count, werden len-Zeichen im Puffer gespeichert, kein Nullterminator wird angehängt und len wird zurückgegeben.

  • Wenn len> count, werden die Zählzeichen im Puffer gespeichert, es wird kein Nullterminator angehängt und ein negativer Wert zurückgegeben.

(...)

Visual Studio 2015 (VC14) hat anscheinend die konforme snprintfFunktion eingeführt, aber die ältere mit dem führenden Unterstrich und dem nicht nullterminierenden Verhalten ist immer noch vorhanden:

Die snprintfFunktion schneidet die Ausgabe ab, wenn len größer oder gleich count ist, indem ein Nullterminator auf gesetzt wird buffer[count-1]. (...)

Für alle Funktionen andere als snprintf, wenn len = zählen, len Zeichen im Puffer gespeichert sind, wird kein Nullabschluss angefügt , (...)

Martin Ba
quelle
22
Was im Namen von Aslan dachten die Microsoft-Ingenieure bei ihrer Einführung, bei _snprintfdem ein wichtiges Sicherheitsmerkmalsnprintf stillschweigend entfernt wird und die Zeichenfolge nicht nullterminiert werden kann?!
Colin D Bennett
2
@ColinDBennett - es ist seltsam und mächtig nervig und ich habe keine Ahnung, ob jemand überhaupt gedacht hat :-)
Martin Ba
2
@MartinBa Ja, sorry, was ich getestet habe, war template <size_t size> int _snprintf_s(char (&buffer)[size], size_t count, const char *format [, argument] ...);und ich sollte auch erwähnen, dass dies nur mit dem Kompilierungsflag / GS (Security Check) passiert. Diese Funktion kennt Größe, Anzahl und Länge.
sekmet64
3
Beachten Sie, dass mingw64 die Microsoft _snprintf-Implementierung als "normalen" snprintf verwendet (verwendet?), Sofern nicht anders angegeben. Nvd.nist.gov/vuln/detail/CVE-2018-1000101
domenukk
2
@Sajjon Es ist ein zugegebenermaßen alberner (und vielleicht völlig origineller) Ausruf der Verzweiflung ( idioms.thefreedictionary.com/in+the+name+of+God ), vielleicht leicht als gehackter Eid ( en.wikipedia.org/wiki/Minced_oath ). Ein anderes Beispiel könnte "Was im Namen von Zeus ...?!" ( forum.wordreference.com/threads/in-the-name-of-zeus.2132965 )
Colin D Bennett
19

Laut snprintf (3) Manpage.

Die Funktionen snprintf()und vsnprintf()schreiben höchstens sizeBytes (einschließlich des nachfolgenden Null-Bytes ('\ 0')) in str.

Also, ja, keine Notwendigkeit zu beenden, wenn Größe> = 1.

piotr
quelle
3
Und Gott sei Dank dafür; Dies ist das einzig sinnvolle Design. Der Sinn der überprüften Versionen dieser Funktionen ist es, sicher zu sein , und es wäre schrecklich, wenn Sie alle Abbruchfehler von Hand ausführen müssten.
Kerrek SB
1
Ich würde empfehlen, es auf den von Ihnen verwendeten Plattformen zu testen, bevor Sie sich darauf verlassen. Selbst wenn es das Null-Byte schreiben sollte , weiß ich, dass ich auf Implementierungen gestoßen bin, die dies nicht getan haben (möglicherweise mit MinGW, das eine ältere MS-Laufzeit verwendet hat).
Dmitri
10

Gemäß dem C-Standard, es sei denn, die Puffergröße ist 0 vsnprintf()und snprintf()null beendet die Ausgabe.

Die snprintf()Funktion muss äquivalent zu sein sprintf(), wobei das Argument n hinzugefügt wird, das die Größe des Puffers angibt, auf den sich s bezieht. Wenn n Null ist, darf nichts geschrieben werden und s kann ein Nullzeiger sein. Andernfalls werden Ausgabebytes jenseits von n-1 verworfen, anstatt in das Array geschrieben zu werden, und am Ende der tatsächlich in das Array geschriebenen Bytes wird ein Nullbyte geschrieben.

Wenn Sie also wissen möchten, wie groß ein zuzuweisender Puffer ist, verwenden Sie eine Größe von Null, und Sie können dann einen Nullzeiger als Ziel verwenden. Beachten Sie, dass ich auf die POSIX-Seiten verlinkt habe, aber diese sagen ausdrücklich, dass es keine Abweichungen zwischen Standard C und POSIX geben soll, wenn sie denselben Grund abdecken:

Die auf dieser Referenzseite beschriebenen Funktionen entsprechen dem ISO C-Standard. Ein Konflikt zwischen den hier beschriebenen Anforderungen und der ISO C-Norm ist unbeabsichtigt. Dieses Volumen von POSIX.1-2008 entspricht dem ISO C-Standard.

Seien Sie vorsichtig mit der Microsoft-Version von vsnprintf(). Es verhält sich definitiv anders als die Standard-C-Version, wenn nicht genügend Speicherplatz im Puffer vorhanden ist (es gibt -1 zurück, wobei die Standardfunktion die erforderliche Länge zurückgibt). Es ist nicht ganz klar, dass die Microsoft-Version null ihre Ausgabe unter Fehlerbedingungen beendet, während dies bei der Standard-C-Version der Fall ist.

Beachten Sie auch die Antworten auf Verwenden Sie die sicheren Funktionen des TR 24731? (siehe MSDN für die Microsoft-Version der vsprintf_s()) und Mac-Lösung für die sicheren Alternativen zu unsicheren C-Standardbibliotheksfunktionen?

Jonathan Leffler
quelle
Oh, böse, habe nie daran gedacht. Auf der anderen Seite ... :)
Prof. Falken
Ah, ich glaube, MS vsprintf () hat mich gebissen, und ich habe diese - 1 Angewohnheit
Prof. Falken
4

Einige ältere Versionen von SunOS haben seltsame Dinge mit snprintf gemacht und möglicherweise die Ausgabe nicht durch NUL beendet und Rückgabewerte erhalten, die nicht mit denen aller anderen übereinstimmten, aber alles, was in den letzten 10 Jahren veröffentlicht wurde, hat das getan, was C99 sagt.

Kunst
quelle
Ich stelle fest, dass XP vor etwas mehr als 10 Jahren veröffentlicht wurde. :-)
Prof. Falken
Und dieses Jahr war es veraltet. :)
Prof. Falken
4

Die Mehrdeutigkeit geht vom C-Standard selbst aus. Sowohl C99 als auch C11 haben eine identische snprintfFunktionsbeschreibung. Hier ist die Beschreibung von C99:

7.19.6.5 Die snprintfFunktion
Synopsis
1 #include <stdio.h> int snprintf(char * restrict s, size_t n, const char * restrict format, ...);
Beschreibung
2 Die snprintfFunktion entspricht fprintf, außer dass die Ausgabe in ein Array (durch Argument angegeben s) und nicht in einen Stream geschrieben wird. Wenn nNull ist, wird nichts geschrieben und skann ein Nullzeiger sein. Andernfalls werden Ausgabezeichen jenseits von n-1st verworfen, anstatt in das Array geschrieben zu werden, und am Ende der tatsächlich in das Array geschriebenen Zeichen wird ein Nullzeichen geschrieben. Wenn zwischen überlappenden Objekten kopiert wird, ist das Verhalten undefiniert.
Returns
3 Die snprintfFunktion gibt die Anzahl der Zeichen , die geschrieben wurden , hättenausreichend groß gewesen ist, ohne das abschließende Nullzeichen oder einen negativen Wert, wenn ein Codierungsfehler aufgetreten ist. Daher wurde die nullterminierte Ausgabe genau dann vollständig geschrieben, wenn der zurückgegebene Wert nicht negativ und kleiner als ist n.

Einerseits der Satz

Andernfalls werden Ausgabezeichen jenseits von n-1st verworfen, anstatt in das Array geschrieben zu werden, und am Ende der tatsächlich in das Array geschriebenen Zeichen wird ein Nullzeichen geschrieben

sagt, dass
wenn (die sPunkte auf ein 3 Zeichen langes Array und) n3 sind, 2 Zeichen geschrieben werden und die Zeichen jenseits des zweiten verworfen werden ; dann wird das Nullzeichen nach diesen 2 geschrieben (und das Nullzeichen ist das 3. geschriebene Zeichen) .

Und das beantwortet meiner Meinung nach die ursprüngliche Frage.
DIE ANTWORT:
Wenn zwischen überlappenden Objekten kopiert wird, ist das Verhalten undefiniert.
Wenn n0 ist, wird nichts in die Ausgabe geschrieben,
andernfalls wird die Ausgabe IMMER nullterminiert , wenn keine Codierungsfehler aufgetreten sind ( unabhängig davon, ob die Ausgabe in das Ausgabearray passt oder nicht ; wenn nicht, werden einige Zeichen so verworfen, dass die Ausgabe erfolgt Array wird nie überflogen),
andernfalls (wenn Codierungsfehler auftreten) kann die Ausgabe nicht nullterminiert bleiben .

Auf der anderen Seite
Der letzte Satz

Daher wurde die nullterminierte Ausgabe genau dann vollständig geschrieben, wenn der zurückgegebene Wert nicht negativ und kleiner als ist n

gibt Mehrdeutigkeit (oder mein Englisch ist nicht gut genug). Ich kann diesen Satz auf mindestens zwei Arten interpretieren:
1. Die Ausgabe wird genau dann nullterminiert , wenn der zurückgegebene Wert nicht negativ und kleiner als istn (was bedeutet, dass wenn der zurückgegebene Wert nicht kleiner als ist n, dh die Ausgabe (einschließlich der) Nullzeichen beenden) passt nicht in das Array, dann ist die Ausgabe nicht nullterminiert ).
2. Die Ausgabe ist genau dann abgeschlossen (es wurden keine Zeichen verworfen), wenn der zurückgegebene Wert nicht negativ und kleiner als istn .


Ich glaube, dass die obige Interpretation 1 DER ANTWORT widerspricht, Missverständnisse und langwierige Diskussionen hervorruft. Aus diesem Grund muss der letzte Satz, der die snprintfFunktion beschreibt, geändert werden, um Unklarheiten zu beseitigen (was Anlass gibt, einen Vorschlag für den C-Sprachstandard zu schreiben).
Das Beispiel für eine nicht mehrdeutige Formulierung, von der ich glaube, kann http://en.cppreference.com/w/c/io/fprintf (siehe 4)) entnommen werden , danke an @ "Martin Ba" für den Link.

Siehe auch die Frage " snprintf: Gibt es C-Standardvorschläge / Pläne zur Änderung der Beschreibung dieser Funktion? ".

Robin Kuzmin
quelle
4
Ihre Interpretation 1 erscheint mir überhaupt nicht plausibel. Ich analysiere diesen Satz als "Die Ausgabe (die übrigens nullterminiert ist) wurde vollständig geschrieben, wenn ...", was ich nur als # 2 verstehen kann.
zwol
1
Die Negation des Satzes "nullterminierte Ausgabe wurde vollständig geschrieben" lautet "nullterminierte Ausgabe wurde nicht vollständig geschrieben". Nichts mehr. Der negierte Satz an sich bedeutet nicht, dass etwas geschrieben wurde (dies schließt unvollständige nullterminierte Ausgabe, unvollständige nicht nullterminierte Ausgabe oder farblose grüne Ideen ein). Eine andere Stelle im Standard gibt an, was genau geschrieben wird, wenn die Ausgabe unvollständig ist, und diese Stelle gibt an, dass die Ausgabe nullterminiert ist, es sei denn, sie ist leer (n == 0).
n. 'Pronomen' m.