Skip to content

Nachteile von scanf

Wir suchen im Internet nach der Lösung für Ihr Dilemma, im Zweifelsfall hinterlassen Sie uns die Frage und wir werden sie gerne beantworten.

Lösung:

Die Probleme mit scanf sind (mindestens):

  • Verwendung von . %s um eine Zeichenkette vom Benutzer zu erhalten, was zu der Möglichkeit führt, dass die Zeichenkette länger als der Puffer ist, was zu einem Überlauf führt.
  • die Möglichkeit, dass ein fehlgeschlagener Scan den Dateizeiger an einer unbestimmten Stelle zurücklässt.

Ich bevorzuge sehr die Verwendung von fgets zu verwenden, um ganze Zeilen einzulesen, damit man die Menge der gelesenen Daten begrenzen kann. Wenn Sie einen 1K-Puffer haben und eine Zeile mit fgets einliest, kann man erkennen, ob die Zeile zu lang war, weil es kein abschließendes Zeilenumbruchzeichen gibt (die letzte Zeile einer Datei ohne Zeilenumbruch einmal ausgenommen).

Dann können Sie sich beim Benutzer beschweren oder mehr Platz für den Rest der Zeile zuweisen (gegebenenfalls so lange, bis Sie genug Platz haben). In beiden Fällen besteht keine Gefahr eines Pufferüberlaufs.

Sobald Sie die Zeile eingelesen haben, können Sie wissen Sie dass du in der nächsten Zeile stehst, also gibt es da kein Problem. Du kannst dann sscanf nach Herzenslust schreiben, ohne den Dateizeiger speichern und wiederherstellen zu müssen, um ihn erneut zu lesen.

Hier ist ein Codeschnipsel, den ich häufig verwende, um sicherzustellen, dass kein Pufferüberlauf entsteht, wenn ich den Benutzer nach Informationen frage.

Es könnte leicht angepasst werden, um eine andere Datei als die Standardeingabe zu verwenden, wenn es nötig ist, und man könnte auch dafür sorgen, dass es seinen eigenen Puffer alloziert (und ihn so lange vergrößert, bis er groß genug ist), bevor es ihn an den Aufrufer zurückgibt (obwohl der Aufrufer dann natürlich dafür verantwortlich wäre, ihn wieder freizugeben).

#include 
#include 

#define OK         0
#define NO_INPUT   1
#define TOO_LONG   2
#define SMALL_BUFF 3
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Size zero or one cannot store enough, so don't even
    // try - we need space for at least newline and terminator.

    if (sz < 2)
        return SMALL_BUFF;

    // Output prompt.

    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }

    // Get line with buffer overrun protection.

    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // Catch possibility of `` in the input stream.

    size_t len = strlen(buff);
    if (len < 1)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.

    if (buff[len - 1] != 'n') {
        extra = 0;
        while (((ch = getchar()) != 'n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[len - 1] = '';
    return OK;
}

Und einen Testtreiber dafür:

// Test program for getLine().

int main (void) {
    int rc;
    char buff[10];

    rc = getLine ("Enter string> ", buff, sizeof(buff));
    if (rc == NO_INPUT) {
        // Extra NL since my system doesn't output that on EOF.
        printf ("nNo inputn");
        return 1;
    }

    if (rc == TOO_LONG) {
        printf ("Input too long [%s]n", buff);
        return 1;
    }

    printf ("OK [%s]n", buff);

    return 0;
}

Schließlich noch ein Testlauf, um ihn in Aktion zu zeigen:

$ printf "" | ./tstprg     # Singular NUL in input stream.
Enter string>
No input

$ ./tstprg < /dev/null       # EOF in input stream.
Enter string>
No input

$ ./tstprg                   # A one-character string.
Enter string> a
OK [a]

$ ./tstprg                   # Longer string but still able to fit.
Enter string> hello
OK [hello]

$ ./tstprg                   # Too long for buffer.
Enter string> hello there
Input too long [hello the]

$ ./tstprg                   # Test limit of buffer.
Enter string> 123456789
OK [123456789]

$ ./tstprg                   # Test just over limit.
Enter string> 1234567890
Input too long [123456789]

Die meisten der bisherigen Antworten scheinen sich auf das Problem des String-Pufferüberlaufs zu konzentrieren. In Wirklichkeit können die Formatspezifikationen, die mit scanf Funktionen verwendet werden können, unterstützen explizite Feldbreite Einstellung, die die maximale Größe der Eingabe begrenzt und einen Pufferüberlauf verhindert. Dies macht die beliebten Anschuldigungen von String-Buffer-Overflow-Gefahren, die in scanf praktisch unbegründet. Die Behauptung, dass scanf irgendwie analog ist zu gets ist in dieser Hinsicht völlig falsch. Es gibt einen großen qualitativen Unterschied zwischen scanf und . gets: scanf bietet dem Benutzer Funktionen zur Verhinderung eines String-Buffer-Overflows, während gets dies nicht tut.

Man kann argumentieren, dass diese scanf Funktionen schwierig zu verwenden sind, da die Feldbreite in die Formatzeichenkette eingebettet werden muss (es gibt keine Möglichkeit, sie durch ein variadisches Argument zu übergeben, wie es in printf). Das ist tatsächlich wahr. scanf ist in dieser Hinsicht in der Tat eher schlecht konzipiert. Aber trotzdem wird behauptet, dass scanf irgendwie hoffnungslos kaputt ist, was die Sicherheit von String-Buffer-Overflows angeht, sind völlig falsch und werden normalerweise von faulen Programmierern gemacht.

Das eigentliche Problem mit scanf ist ganz anderer Natur, auch wenn es sich ebenfalls um Überlauf. Wenn scanf Funktion zur Umwandlung von Dezimaldarstellungen von Zahlen in Werte arithmetischer Typen verwendet wird, bietet sie keinen Schutz vor arithmetischem Überlauf. Wenn ein Überlauf auftritt, scanf ein undefiniertes Verhalten. Aus diesem Grund ist der einzige geeignete Weg, die Konvertierung in der C-Standardbibliothek durchzuführen, die Funktionen von strto... Familie.

Zusammenfassend lässt sich also sagen, dass das Problem mit scanf ist, dass es schwierig (wenn auch möglich) ist, es richtig und sicher mit String-Puffern zu verwenden. Und es ist unmöglich, sie sicher für arithmetische Eingaben zu verwenden. Letzteres ist das eigentliche Problem. Das erste ist nur eine Unannehmlichkeit.

P.S. Die obigen Ausführungen beziehen sich auf die gesamte Familie der scanf Funktionen (darunter auch fscanf und sscanf). Mit scanf besteht das offensichtliche Problem darin, dass die Idee, eine streng formatierte Funktion zum Lesen von potenziell interaktiven Eingaben eher fragwürdig ist.

Aus der comp.lang.c FAQ: Warum sagt jeder, dass man scanf nicht benutzen soll? Was sollte ich stattdessen verwenden?

scanf hat eine Reihe von Problemen - siehe Fragen 12.17, 12.18a und 12.19. Auch sein %s Format hat das gleiche Problem wie gets() hat (siehe Frage 12.23) - es ist schwer zu garantieren, dass der Empfangspuffer nicht überläuft. [footnote]

Allgemeiner ausgedrückt, scanf ist für relativ strukturierte, formatierte Eingaben gedacht (sein Name leitet sich tatsächlich von "scan formatted" ab). Wenn Sie gut aufpassen, wird es Ihnen sagen, ob es erfolgreich war oder fehlgeschlagen ist, aber es kann Ihnen nur ungefähr sagen, wo es fehlgeschlagen ist, und überhaupt nicht wie oder warum. Sie haben nur sehr wenig Möglichkeiten, den Fehler zu beheben.

Dabei ist die interaktive Benutzereingabe die am wenigsten strukturierte Eingabe, die es gibt. Eine gut gestaltete Benutzeroberfläche lässt die Möglichkeit zu, dass der Benutzer so ziemlich alles eingibt - nicht nur Buchstaben oder Satzzeichen, wenn Ziffern erwartet wurden, sondern auch mehr oder weniger Zeichen als erwartet oder gar keine Zeichen (d.h.., nur die RETURN-Taste), oder ein vorzeitiges EOF, oder sonst etwas. Es ist fast unmöglich, mit all diesen potentiellen Problemen elegant umzugehen, wenn man scanfzu umgehen; es ist viel einfacher, ganze Zeilen zu lesen (mit fgets oder ähnlichem) zu lesen und sie dann zu interpretieren, entweder mit sscanf oder einer anderen Technik. (Funktionen wie strtol, strtok, und atoi sind oft nützlich; siehe auch Fragen 12.16 und 13.6.) Wenn Sie eine der folgenden Funktionen verwenden scanf Variante verwenden, sollten Sie den Rückgabewert überprüfen, um sicherzustellen, dass die erwartete Anzahl von Elementen gefunden wurde. Auch wenn Sie %sverwenden, müssen Sie sich vor einem Pufferüberlauf schützen.

Nebenbei bemerkt, die Kritik an scanf nicht unbedingt eine Anklage gegen fscanf und sscanf. scanf liest aus stdindie in der Regel eine interaktive Tastatur ist und daher am wenigsten Beschränkungen unterliegt, was zu den meisten Problemen führt. Wenn eine Datendatei ein bekanntes Format hat, kann es dagegen sinnvoll sein, sie mit fscanf. Es ist durchaus angebracht, Zeichenketten zu parsen mit sscanf zu parsen (solange der Rückgabewert geprüft wird), weil es so einfach ist, die Kontrolle wiederzuerlangen, den Scan neu zu starten, die Eingabe zu verwerfen, wenn sie nicht übereinstimmt, usw.

Weitere Links:

  • längere Erklärung von Chris Torek
  • längere Erklärung von meiner Wenigkeit

Quellenangaben: K&R2 Abschn. 7.4 S. 159

Abschnitt Rezensionen und Bewertungen

Sie können unsere Informationen aufwerten, indem Sie Ihre Erfahrungen in die Beobachtungen einbringen.



Nutzen Sie unsere Suchmaschine

Suche
Generic filters

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.