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
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.
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
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
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
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
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
fflushEingabestrom 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
14103300cookie-checkWarum verursacht scanf() in diesem Code eine Endlosschleife?yes
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 vonc
sollte seinint
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