Warum verursacht scanf() in diesem Code eine Endlosschleife?

Lesezeit: 7 Minuten

Ich habe ein kleines C-Programm, das nur Zahlen aus stdin liest, eine bei jedem Schleifenzyklus. Wenn der Benutzer etwas NaN eingibt, sollte ein Fehler auf der Konsole ausgegeben werden und die Eingabeaufforderung sollte erneut zurückkehren. Bei Eingabe von “0” sollte die Schleife enden und die Anzahl der gegebenen positiven/negativen Werte auf der Konsole ausgegeben werden. Hier ist das Programm:

#include <stdio.h>

int main()
{
    int number, p = 0, n = 0;

    while (1) {
        printf("-> ");
        if (scanf("%d", &number) == 0) {
            printf("Err...\n");
            continue;
        }
        
        if (number > 0) p++;
        else if (number < 0) n++;
        else break; /* 0 given */
    }

    printf("Read %d positive and %d negative numbers\n", p, n);
    return 0;
}

Mein Problem ist, dass die Eingabe einer Nichtzahl (wie “a”) zu einer Endlosschleife führt, in der immer wieder “-> Err …” geschrieben wird. Ich denke, es ist ein Problem mit scanf() und ich weiß, dass diese Funktion durch eine sicherere ersetzt werden könnte, aber dieses Beispiel ist für Anfänger, die nur über printf/scanf, if-else und Schleifen Bescheid wissen.

Die Antworten auf die Frage habe ich bereits gelesenscanf() überspringt jeden anderen while Schleife in C und habe andere Fragen überflogen, aber nichts hat dieses spezifische Problem wirklich beantwortet.

  • Viele eng verwandte SO-Fragen, einschließlich: stackoverflow.com/questions/1669821

    – Jonathan Leffler

    11. November 2009 um 22:34 Uhr

  • Als Antwort auf alle Antworten und Hinweise: Hinzufügen von while (getchar() != ‘\n’); vor “Fortfahren” innerhalb der if-Anweisung funktioniert für mich wirklich gut und löst (hoffentlich) alle / die meisten Probleme. Außerdem ist es für Anfänger vernünftig erklärbar :).

    Benutzer208785

    13. November 2009 um 20:51 Uhr

  • Siehe auch Verwenden fflush(stdin).

    – Jonathan Leffler

    15. September 2016 um 5:32 Uhr

  • Beachten Sie, dass die von user208785 im obigen Kommentar erwähnte Schleife sein sollte int c; while ((c = getchar()) != EOF && c != '\n') ; – Die Art von c sollte sein int und der Code muss sowohl EOF als auch Newline testen, obwohl der Code normalerweise einen Newline vor EOF erhält.

    – Jonathan Leffler

    5. September um 19:31 Uhr

Benutzeravatar von Jamison Dance
Jamison-Tanz

scanf verbraucht nur die Eingabe, die mit der Formatzeichenfolge übereinstimmt, und gibt die Anzahl der verbrauchten Zeichen zurück. Jedes Zeichen, das nicht mit der Formatzeichenfolge übereinstimmt, führt dazu, dass der Scanvorgang beendet wird, und das ungültige Zeichen bleibt im Puffer. Wie andere bereits sagten, müssen Sie das ungültige Zeichen noch aus dem Puffer löschen, bevor Sie fortfahren. Dies ist eine ziemlich schmutzige Lösung, aber sie entfernt die anstößigen Zeichen aus der Ausgabe.

char c="0";
if (scanf("%d", &number) == 0) {
  printf("Err. . .\n");
  do {
    c = getchar();
  }
  while (!isdigit(c));
  ungetc(c, stdin);
  //consume non-numeric chars from buffer
}

bearbeiten: Der Code wurde korrigiert, um alle nicht numerischen Zeichen auf einmal zu entfernen. Druckt nicht mehr mehrere “Fehler” für jedes nicht-numerische Zeichen mehr aus.

Hier ist ein ziemlich guter Überblick über scanf.

  • Wenn die Eingabe “abc” ist, wird dieser Code “Err. . .” drei Mal.

    – Teddy

    11. November 2009 um 15:57 Uhr

  • Ja, es ist ein ziemliches Ghetto. Ich werde es ein bisschen anpassen.

    – Jamison-Tanz

    11. November 2009 um 16:44 Uhr

  • Wenn die Eingabe nun “ab-10” ist, wird das Minuszeichen fälschlicherweise aus der Eingabe entfernt und “10” als nächste Zahl gelesen.

    – Café

    11. November 2009 um 21:21 Uhr

  • Ich weiß, es ist alt, aber ändern Sie es einfach zu while (!isdigit(c) && c != '-');das sollte auch bei Minuszeichen helfen.

    – Michael Armbruster

    24. Mai 2015 um 15:39 Uhr

  • Dies führt immer noch zu mehreren Eingabezeilen, versuchen Sie es 4t und t4, 4t werde dir geben -> Err. . . und t4 gibt Ihnen nicht einmal Fehler, aber immer noch mehrere Eingabezeilen: -> ->

    – Ilgar

    11. September 2015 um 15:16 Uhr

Benutzeravatar von Lucas
Lukas

Ich denke, Sie müssen nur den Puffer leeren, bevor Sie mit der Schleife fortfahren. So etwas würde wahrscheinlich den Job machen, obwohl ich von hier aus nicht testen kann, was ich schreibe:

int c;
while((c = getchar()) != '\n' && c != EOF);

  • “vorzeitige Optimierung ist die Wurzel allen Übels” … aber tausche die Konstanten: '\n' viel wahrscheinlicher auftaucht als EOF 🙂

    – pmg

    11. November 2009 um 15:54 Uhr

  • Du würdest hoffen EOF wird zu 100 % garantiert angezeigt; Andernfalls haben Sie entweder eine wirklich schnelle Tastatur oder eine wirklich langsame CPU

    – Andomar

    11. November 2009 um 16:05 Uhr

  • Die obige Komplikation in der while Die Bedingung der Anweisung ist unnötig.

    – Ilgar

    2. Dezember 2018 um 5:38 Uhr

  • @ilgaar Was meinst du? Für mich sieht es gut aus.

    – Filippo Costa

    24. November 2019 um 0:09 Uhr

Teddys Benutzeravatar
Teddy

scanf() Verlässt die “a” noch im Eingabepuffer für das nächste Mal. Sie sollten wahrscheinlich verwenden getline() um eine Zeile zu lesen, egal was passiert, und sie dann mit zu analysieren strtol() oder ähnliches stattdessen.

(Ja, getline() ist GNU-spezifisch, nicht POSIX. Na und? Die Frage ist mit “gcc” und “linux” gekennzeichnet. getline() ist auch die einzig sinnvolle Möglichkeit, eine Textzeile zu lesen, es sei denn, Sie möchten alles von Hand machen.)

  • Sie können sich für etwas so Wichtiges wie Benutzereingaben nicht auf nicht standardmäßige Erweiterungen verlassen, ohne sie in Ihrem eigenen Baum bereitzustellen, falls sie nicht vorhanden sind. Wenn Sie Ihre Antwort bearbeiten, um dies widerzuspiegeln, werde ich meine Ablehnung zurückziehen.

    – Tim Post

    11. November 2009 um 15:58 Uhr

  • @tinkertim: Die Frage gibt gcc unter Linux an und garantiert dies strtol ist verfügbar

    – Andomar

    11. November 2009 um 16:00 Uhr

  • Außerdem kann es hilfreich sein, zumindest einen Hinweis darauf zu geben, wie man eine solche Erweiterung einschaltet 🙂

    – Tim Post

    11. November 2009 um 16:02 Uhr

  • @Andomar: Es war getline(), mit dem ich Probleme hatte;)

    – Tim Post

    11. November 2009 um 16:03 Uhr

  • @TimPost Sowohl getline() als auch getdelim() waren ursprünglich GNU-Erweiterungen. Sie wurden in POSIX.1-2008 standardisiert.

    – nyuszika7h

    6. September 2014 um 16:14 Uhr

Aufgrund der Probleme mit scanf Wie die anderen Antworten zeigen, sollten Sie wirklich einen anderen Ansatz in Betracht ziehen. Ich habe immer gefunden scanf viel zu begrenzt für ernsthaftes Lesen und Verarbeiten von Eingaben. Es ist eine bessere Idee, einfach ganze Zeilen mit einzulesen fgets und dann mit Funktionen wie bearbeiten strtok und strtol (was BTW Ganzzahlen korrekt analysiert und Ihnen genau sagt, wo die ungültigen Zeichen beginnen).

Anstatt zu verwenden scanf() und mit dem Puffer umgehen müssen, der ungültiges Zeichen hat, verwenden fgets() und sscanf().

/* ... */
    printf("0 to quit -> ");
    fflush(stdout);
    while (fgets(buf, sizeof buf, stdin)) {
      if (sscanf(buf, "%d", &number) != 1) {
        fprintf(stderr, "Err...\n");
      } else {
        work(number);
      }
      printf("0 to quit -> ");
      fflush(stdout);
    }
/* ... */

  • fgets() liest einen Puffer und wenn er nicht von Anfang an das Format enthält, wird die ganze Zeile weggeworfen. Dies könnte nicht akzeptabel sein (aber erwünscht sein, es hängt von den Anforderungen ab).

    – Roman Nikitschenko

    11. November 2009 um 16:55 Uhr

Benutzeravatar von j0k
j0k

Ich hatte ein ähnliches Problem. Ich habe es gelöst, indem ich nur scanf verwendet habe.

Input "abc123<Enter>" um zu sehen, wie es funktioniert.

#include <stdio.h>
int n, num_ok;
char c;
main() {
    while (1) {
        printf("Input Number: ");
        num_ok = scanf("%d", &n);
        if (num_ok != 1) {
            scanf("%c", &c);
            printf("That wasn't a number: %c\n", c);
        } else {
            printf("The number is: %d\n", n);
        }
    }
}

  • fgets() liest einen Puffer und wenn er nicht von Anfang an das Format enthält, wird die ganze Zeile weggeworfen. Dies könnte nicht akzeptabel sein (aber erwünscht sein, es hängt von den Anforderungen ab).

    – Roman Nikitschenko

    11. November 2009 um 16:55 Uhr

Benutzeravatar von Thomas Padron-McCarthy
Thomas Padron-McCarthy

Auf einigen Plattformen (insbesondere Windows und Linux) können Sie verwenden fflush(stdin);:

#include <stdio.h>

int main(void)
{
  int number, p = 0, n = 0;

  while (1) {
    printf("-> ");
    if (scanf("%d", &number) == 0) {
        fflush(stdin);
        printf("Err...\n");
        continue;
    }
    fflush(stdin);
    if (number > 0) p++;
    else if (number < 0) n++;
    else break; /* 0 given */
  }

  printf("Read %d positive and %d negative numbers\n", p, n);
  return 0;
}

  • Bitte lesen Sie Verwenden fflush(stdin) — insbesondere die Kommentare zu der Frage — für diesbezügliche Informationen. Es funktioniert unter Windows, weil Microsoft dokumentiert, dass es funktioniert; Es funktioniert in der Praxis nirgendwo anders (soweit ich weiß), trotz einiger Dokumentationen, die das Gegenteil vermuten lassen.

    – Jonathan Leffler

    4. Januar 2015 um 7:31 Uhr

  • Es funktioniert jetzt unter Linux (oder ich sollte glibc sagen). Früher war das nicht der Fall, und ich weiß nicht, wann sie es geändert haben. Aber das letzte Mal, als ich es auf einem Mac ausprobiert habe, ist es abgestürzt, und es ist nicht im Standard, daher habe ich dieser Antwort eine Warnung zur Portabilität hinzugefügt.

    – Thomas Padron-McCarthy

    7. November 2016 um 8:35 Uhr


  • Funktioniert bei mir hier mit meiner aktuellen Version nicht. $ ldd --version gibt ldd (Debian GLIBC 2.19-18+deb8u9) 2.19. Das sollte alle benötigten Informationen geben. Hat jemand eine Ahnung warum?

    – DrBeco

    26. Mai 2017 um 16:18 Uhr

  • fflush Eingabestrom ist nur für zugeordnete Eingabeströme definiert durchsuchbare Dateien (z.B, Disk-Dateien, aber keine Rohre oder Terminals). POSIX.1-2001 hat das Verhalten für das Leeren von Eingabeströmen nicht spezifiziert, POSIX.1-2008 tut dies, aber nur in der beschriebenen begrenzten Weise.

    – David C. Rankin

    6. Oktober 2017 um 23:12 Uhr

  • verwenden fflush(stdin) führt zu undefiniertem Verhalten und es ist nicht garantiert, dass es portabel funktioniert.

    – Ilgar

    2. Dezember 2018 um 5:32 Uhr


1410330cookie-checkWarum verursacht scanf() in diesem Code eine Endlosschleife?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy