2009-10-01 8 views
9

Der folgende Code verursacht einen Fehler und tötet meine Anwendung. Dies ist sinnvoll, da der Puffer nur 10 Byte lang ist und der Text 22 Byte lang ist (Pufferüberlauf).sprintf_s mit einem Puffer zu klein

Wie erhalte ich diesen Fehler, damit ich es melden kann, anstatt meine Anwendung zu stürzen?

Edit:

Nach dem Lesen der Kommentare unten ich mit _snprintf_s ging. Wenn es einen Wert -1 zurückgibt, wurde der Puffer nicht aktualisiert.

length = _snprintf_s(buffer, 10, 9, "123456789"); 
printf("1) Length=%d\n", length); // Length == 9 

length = _snprintf_s(buffer, 10, 9, "1234567890.1234567890."); 
printf("2) Length=%d\n", length); // Length == -1 

length = _snprintf_s(buffer, 10, 10, "1234567890.1234567890."); 
printf("3) Length=%d\n", length); // Crash, it needs room for the NULL char 
+0

Das Übergeben der Puffergröße und der Puffergröße minus eins ist stumpf und fehleranfällig. Sie sollten die unten beschriebene Variante bevorzugen: length = _snprintf_s (Puffer, _TRUNCATE, "1234567890.1234567890."); Da der erste Größenparameter weggelassen wird, verwendet der Compiler die Template-Überladung, die die Größe ableitet. _TRUNCATE ist ein besonderer Wert, der das leistet, was er sagt. Keine magischen Zahlen, und jetzt ist Ihr Code sicher, wartbar und ein gutes Beispiel. Wenn Sie diesen Kommentar und _snprintf_s mögen, dann sollten Sie meine Antwort anstelle der gefährlichen snprintf/_snprintf Antwort auswählen. –

Antwort

5

Anstelle von sprintf_s können Sie snprintf (a.k.a _snprintf unter Windows) verwenden.

#ifdef WIN32 
#define snprintf _snprintf 
#endif 

char buffer[10];  
int length = snprintf(buffer, 10, "1234567890.1234567890."); 
// unix snprintf returns length output would actually require; 
// windows _snprintf returns actual output length if output fits, else negative 
if (length >= sizeof(buffer) || length<0) 
{ 
    /* error handling */ 
} 
+4

Es gibt auch eine snprintf_s. – Joe

+2

Hinweis: Aus Sicherheitsgründen ist der Inhalt des Puffers möglicherweise nicht null-terminiert, wenn nicht genügend Platz vorhanden ist. – Managu

+2

@Managu: Wenn MS die Konformität mit C99 beanspruchte - was nicht der Fall ist - wäre diese Behauptung falsch; Der C99-Standard erfordert snprintf(), um den String null zu beenden, wenn die Länge des Strings 0 ist. Abschnitt 7.19.6.5: Wenn n gleich Null ist, wird nichts geschrieben ... Ansonsten werden Ausgabezeichen jenseits des n-1st eher verworfen als als in das Array geschrieben wird, und ein Nullzeichen wird am Ende der tatsächlich in das Array geschriebenen Zeichen geschrieben. Wenn das Kopieren zwischen Objekten stattfindet, die sich überlappen, ist das Verhalten nicht definiert. –

0

von MSDN:

Der andere Hauptunterschied zwischen sprintf_s und sprintf ist, dass eine Länge sprintf_s Parameter nimmt in Zeichen, die Größe des Ausgangspuffers spezifizieren. 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 wird aufgerufen. Im Gegensatz zu snprintf garantiert sprintf_s, dass der Puffer null-terminiert ist (es sei denn, die Puffergröße ist Null).

Also idealerweise sollte das, was Sie geschrieben haben, richtig funktionieren.

+4

Der standardmäßige "ungültige Parameterhandler" beendet den Prozess. –

+0

true, aber es ist einfach zu installieren, das nicht, was dazu führt, dass sprintf_s -1 zurückgibt, wenn der Puffer zu klein ist – stijn

0

Sieht aus, als würdest du auf MSVC schreiben?

Ich denke, die MSDN-Dokumentation für sprintf_s sagt, dass es behauptet stirbt, so dass ich nicht sicher bin, ob Sie das programmatisch abfangen können.

Wie LBushkin vorgeschlagen hat, ist es viel besser, wenn Sie Klassen verwenden, die die Strings verwalten.

16

Es ist von Entwurf. Der gesamte Punkt von sprintf_s und anderen Funktionen aus der -Familie besteht darin, Buffer-Overrun-Fehler abzufangen und sie als Vorbedingungsverletzungen zu behandeln. Dies bedeutet, dass sie nicht wirklich wiederherstellbar sein sollen. Dies dient nur dazu, Fehler abzufangen - Sie sollten niemals sprintf_s aufrufen, wenn Sie wissen, dass die Zeichenfolge für einen Zielpuffer zu groß sein kann. Verwenden Sie in diesem Fall zuerst strlen, um zu prüfen, ob Sie eine Trimmung durchführen müssen.

+0

Ich stimme nicht zu. Es ist durchaus sinnvoll, sprintf_s mit einem zu kleinen Zielpuffer aufzurufen, solange Sie das Flag _TRUNCATE verwenden, um dies anzuzeigen. Okay, technisch _TRUNCATE benötigt snprintf_s anstelle von sprintf_s, aber mein Punkt steht meistens. Die Verwendung von Strlen ist oft nicht anwendbar oder unbequem, aber die Verwendung von _TRUNCATE ist oft trivial und angemessen. –

+0

Ich denke, dass die Verwendung von 'snprintf_s' der entscheidende Unterschied ist, und nicht wirklich eine bloße Technik. –

+0

Es ist definitiv ein entscheidender Unterschied. Aber ich denke, Ihre Antwort lässt es so aussehen, als ob die Familie der Funktionen nicht abgeschnitten werden kann, was irreführend sein könnte. –

0

Siehe Abschnitt 6.6.1 von TR24731, der die ISO C Committee Version der von Microsoft implementierten Funktionalität ist. Es bietet Funktionen set_constraint_handler(), abort_constraint_handler() und ignore_constraint_handler() Funktionen. Es gibt Kommentare von Pavel Minaev, die darauf hindeuten, dass die Microsoft-Implementierung nicht dem TR24731-Vorschlag entspricht (was ein "Typ-2-Tech-Bericht" ist), sodass Sie möglicherweise nicht eingreifen können oder etwas unternehmen müssen anders als das, was der TR angibt, sollte getan werden. Untersuchen Sie MSDN.

+1

Leider implementiert MSVC TR24731 nicht vollständig - insbesondere implementiert es nicht speziell die Funktionen, die Sie referenzieren (auch ihre Namen enden mit '_s' - also' set_constraint_handler_s'). –

+1

Aber nach http://msdn.microsoft.com/en-us/library/ksazx244%28VS.80%29.aspx gibt es eine Funktion _set_invalid_parameter_handler() -Funktion, die verwendet werden kann, um das Standardverhalten des Abbrechens des Programms zu ändern . –

5

Dies funktioniert mit VC++ und ist sogar sicherer als snprintf mit (und sicherlich sicherer als _snprintf):

void TestString(const char* pEvil) 
{ 
    char buffer[100]; 
    _snprintf_s(buffer, _TRUNCATE, "Some data: %s\n", pEvil); 
} 

Die _TRUNCATE Flagge zeigt an, dass die Zeichenfolge abgeschnitten werden sollte. In dieser Form wird die Größe des Puffers nicht wirklich übergeben, was (paradoxerweise!) macht es so sicher. Der Compiler verwendet Vorlagenmagie, um die Puffergröße zu ermitteln, was bedeutet, dass sie nicht falsch angegeben werden kann (ein überraschend häufiger Fehler). Diese Technik kann angewendet werden, um andere sichere Zeichenfolgen-Wrapper zu erstellen, wie in meinem Blogpost hier beschrieben: https://randomascii.wordpress.com/2013/04/03/stop-using-strncpy-already/

+0

Wenn Sie die MSDN-Dokumentation von _snprintf_s betrachten, scheint es, dass Sie ein Argument im Aufruf von _snprintf_s vergessen haben. Dieses Argument sollte zwischen Puffer und _TRUNCATE erscheinen und heißt sizeOfBuffer – user1741137

+1

Ich habe ein Argument nicht vergessen - dieser Code kompiliert und ist vollkommen sicher. Sie müssen die Dokumentation erneut lesen. Ich verwende eine Template-Überschreibung für _snprintf_s, die den Compiler anweist, die Puffergröße abzuleiten. Ich habe Hunderte von Orten gesehen, wo Programmierer explizit in einer Puffergröße übergeben und in der * falschen * Größe übergeben haben. Nur wenn der Compiler die Puffergröße ableitet, kann dieser schwerwiegende Fehler vermieden werden. Ich habe auf diese Technik in dem Artikel hingewiesen, den ich in meiner Lösung verlinkt habe. Wärmstens empfohlen. –