Mit fflush (stdin)

77

Eine schnelle Google-Suche zum fflush(stdin)Löschen des Eingabepuffers zeigt also zahlreiche Websites an, die davor warnen, ihn zu verwenden. Und doch hat mein CS-Professor der Klasse genau das beigebracht.

Wie schlecht ist die Verwendung fflush(stdin)? Sollte ich wirklich darauf verzichten, obwohl mein Professor es benutzt und es einwandfrei zu funktionieren scheint?

falscher Benutzername
quelle
8
Codinghorror.com/blog/2007/03/…
BlueRaja - Danny Pflughoeft
8
Sowohl Windows als auch Linux definieren das Verhalten fflush()eines Eingabestreams und definieren es sogar auf die gleiche Weise (Wunder der Wunder). Die POSIX-, C- und C ++ - Standards für fflush()definieren das Verhalten nicht, aber keiner von ihnen hindert ein System daran, es zu definieren. Wenn Sie Codierung für maximale Portabilität, zu vermeiden fflush(stdin); Wenn Sie für Plattformen codieren, die das Verhalten definieren, verwenden Sie es. Beachten Sie jedoch, dass es nicht portierbar ist.
Jonathan Leffler
Cygwin ist ein Beispiel für eine ziemlich verbreitete Plattform, auf der fflush(stdin);die Eingabe nicht gelöscht wird.
MM
2
Es hängt auch genau davon ab, was Sie erwarten fflush(stdin).
Keith Thompson
@JonathanLeffler Das Windows-Dokument sagt If the stream was opened in read mode, or if the stream has no buffer, the call to fflush has no effect, and any buffer is retained, und das Linux-Dokument sagt, For input streams, fflush() discards any buffered data that has been fetched from the underlying file, but has not been consumed by the application.dass dies nicht genau der gleiche Weg ist. Windows behält den Puffer bei und Linux verwirft den Puffer.
ssbssa

Antworten:

76

Einfach: Dies ist ein undefiniertes Verhalten, da fflushes für einen Ausgabestream aufgerufen werden soll. Dies ist ein Auszug aus dem C-Standard:

int fflush (FILE * ostream);

ostream verweist auf einen Ausgabestream oder einen Aktualisierungsstrom, in den der letzte Vorgang nicht eingegeben wurde. Die Funktion fflush bewirkt, dass ungeschriebene Daten für diesen Stream an die Hostumgebung gesendet und in die Datei geschrieben werden. Andernfalls ist das Verhalten undefiniert.

Es geht also nicht darum, "wie schlimm" das ist. fflush(stdin)ist eindeutig falsch , und Sie dürfen es niemals benutzen .

Eli Bendersky
quelle
16
@BlueRaja: Hier gibt es Verteidigung für einen Anfängerfehler, aber keine Verteidigung für einen Lehrer, der falsches Wissen verbreitet! Jeder Verweis auf fflushmacht deutlich, dass er für Ausgabestreams direkt im ersten Absatz gedacht ist. Sie müssen sich dafür nicht den C-Standard merken!
Eli Bendersky
6
@Eli: Niemand kann alles wissen. Der Prozessor wird seinen Fehler nie erfahren, bis ihm jemand sagt ... Ich habe ihn fflush(stdin)jahrelang benutzt, bis ich
herausfand
4
Ähm, sollte man normalerweise nicht die Dokumentation einer Funktion konsultieren, bevor sie verwendet wird? Besonders ein Professor?
Alex Budovski
6
Ein weiterer Verteidigungspunkt wäre der folgende Teil der Manpage (verschiedene glibc-Versionen unter Linux): "Bei Eingabestreams werden fflush()alle gepufferten Daten verworfen, die aus der zugrunde liegenden Datei abgerufen, aber nicht von der Anwendung verwendet wurden. Das Öffnen Der Status des Streams bleibt davon unberührt. " Obwohl es sich um UB handelt, scheinen einige Implementierungen Garantien zu geben, ohne den Status in Bezug auf den Standard zu erwähnen.
Daniel Fischer
4
Es gibt einen anderen Aspekt, den ich selten erwähnt sehe: Er fflush(stdin)ist viel schlimmer als nur ein implementierungsdefiniertes Verhalten. Selbst wenn es so funktionieren würde, wie es die meisten Menschen beabsichtigen, wäre es schrecklich. Stellen Sie sich vor, stdin ist nicht jemand, der dumm Eingaben eingibt, sondern von einem anderen Programm oder einer anderen Shell-Umleitung stammt: Es würde den Anfang der Datei lesen und dann einfach den Rest löschen. Es ist wirklich dumm zu denken, dass stdin als menschlicher Bediener immer etwas so Langsames ist.
Rafael Lerm
41

Konvertieren von Kommentaren in eine Antwort - und Erweitern dieser Kommentare, da das Problem regelmäßig erneut auftritt.

Standard C und POSIX bleiben fflush(stdin)als undefiniertes Verhalten

Die POSIX- , C- und C ++ - Standards für geben fflush()explizit an, dass das Verhalten undefiniert ist, aber keiner von ihnen hindert ein System daran, es zu definieren.

ISO / IEC 9899: 2011 - die C11-Norm - sagt:

§7.21.5.2 Die fflush-Funktion

¶2 Wenn streamauf einen Ausgabestream oder einen Aktualisierungsdatenstrom verwiesen wird, in den der letzte Vorgang nicht eingegeben wurde, fflushbewirkt die Funktion, dass ungeschriebene Daten für diesen Datenstrom an die Hostumgebung gesendet und in die Datei geschrieben werden. Andernfalls ist das Verhalten undefiniert.

POSIX weicht größtenteils vom C-Standard ab, markiert diesen Text jedoch als C-Erweiterung.

[CX] Wenn für einen zum Lesen geöffneten Stream die Datei noch nicht bei EOF ist und die Datei gesucht werden kann, wird der Dateiversatz der zugrunde liegenden Beschreibung der geöffneten Datei auf die Dateiposition des Streams und auf einen beliebigen Wert gesetzt Zeichen, die von dem Stream zurückgeschoben wurden ungetc()oder ungetwc()die später nicht aus dem Stream gelesen wurden, werden verworfen (ohne den Datei-Offset weiter zu ändern).

Beachten Sie, dass Terminals nicht suchen können. Weder sind Rohre oder Muffen.

Microsoft definiert das Verhalten von fflush(stdin)

Microsoft und die Visual Studio-Laufzeit definieren das Verhalten fflush()eines Eingabestreams.

Wenn der Stream für Eingaben geöffnet ist, wird fflushder Inhalt des Puffers gelöscht.

MM- Hinweise :

Cygwin ist ein Beispiel für eine ziemlich verbreitete Plattform, auf der fflush(stdin)die Eingabe nicht gelöscht wird.

Aus diesem Grund vermerkt diese Antwortversion meines Kommentars "Microsoft und die Visual Studio-Laufzeit". Wenn Sie eine Nicht-Microsoft C-Laufzeitbibliothek verwenden, hängt das angezeigte Verhalten von dieser Bibliothek ab.

Linux-Dokumentation und -Praxis scheinen sich zu widersprechen

Überraschenderweise dokumentiert Linux nominell auch das Verhalten von fflush(stdin)und definiert es sogar auf die gleiche Weise (Wunder der Wunder).

Bei Eingabestreams werden fflush()alle gepufferten Daten verworfen, die aus der zugrunde liegenden Datei abgerufen, aber nicht von der Anwendung verwendet wurden.

Ich bin immer noch etwas verwirrt und überrascht über die Linux-Dokumentation, die besagt, dass dies fflush(stdin)funktionieren wird. Trotz dieses Vorschlags funktioniert es normalerweise nicht unter Linux. Ich habe gerade die Dokumentation zu Ubuntu 14.04 LTS überprüft. es sagt, was oben zitiert wurde, aber empirisch funktioniert es nicht - zumindest wenn der Eingabestream ein nicht durchsuchbares Gerät wie ein Terminal ist.

demo-fflush.c

#include <stdio.h>

int main(void)
{
    int c;
    if ((c = getchar()) != EOF)
    {
        printf("Got %c; enter some new data\n", c);
        fflush(stdin);
    }
    if ((c = getchar()) != EOF)
        printf("Got %c\n", c);

    return 0;
}

Beispielausgabe

$ ./demo-fflush
Alliteration
Got A; enter some new data
Got l
$

Diese Ausgabe wurde sowohl unter Ubuntu 14.04 LTS als auch unter Mac OS X 10.11.2 erhalten. Nach meinem Verständnis widerspricht es dem, was das Linux-Handbuch sagt. Wenn die fflush(stdin)Operation funktionieren würde, müsste ich eine neue Textzeile eingeben, um Informationen für die zweite getchar()zu lesen.

In Anbetracht dessen, was der POSIX-Standard sagt, ist möglicherweise eine bessere Demonstration erforderlich, und die Linux-Dokumentation sollte präzisiert werden.

demo-fflush2.c

#include <stdio.h>

int main(void)
{
    int c;
    if ((c = getchar()) != EOF)
    {
        printf("Got %c\n", c);
        ungetc('B', stdin);
        ungetc('Z', stdin);
        if ((c = getchar()) == EOF)
        {
            fprintf(stderr, "Huh?!\n");
            return 1;
        }
        printf("Got %c after ungetc()\n", c);
        fflush(stdin);
    }
    if ((c = getchar()) != EOF)
        printf("Got %c\n", c);

    return 0;
}

Beispielausgabe

Beachten Sie, dass dies /etc/passwdeine durchsuchbare Datei ist. Unter Ubuntu sieht die erste Zeile folgendermaßen aus:

root:x:0:0:root:/root:/bin/bash

Unter Mac OS X sehen die ersten 4 Zeilen folgendermaßen aus:

##
# User Database
# 
# Note that this file is consulted directly only when the system is running

Mit anderen Worten, oben in der Mac OS X- /etc/passwdDatei befindet sich ein Kommentar . Die nicht kommentierten Zeilen entsprechen dem normalen Layout, daher rootlautet der Eintrag:

root:*:0:0:System Administrator:/var/root:/bin/sh

Ubuntu 14.04 LTS:

$ ./demo-fflush2 < /etc/passwd
Got r
Got Z after ungetc()
Got o
$ ./demo-fflush2
Allotrope
Got A
Got Z after ungetc()
Got B
$

Mac OS X 10.11.2:

$ ./demo-fflush2 < /etc/passwd
Got #
Got Z after ungetc()
Got B
$

Das Mac OS X-Verhalten ignoriert das (oder scheint es zumindest zu ignorieren) fflush(stdin)(daher folgt POSIX in diesem Problem nicht). Das Linux-Verhalten entspricht dem dokumentierten POSIX-Verhalten, aber die POSIX-Spezifikation ist in ihren Aussagen weitaus vorsichtiger - sie gibt eine Datei an, die suchen kann, aber Terminals unterstützen natürlich nicht das Suchen. Es ist auch viel weniger nützlich als die Microsoft-Spezifikation.

Zusammenfassung

Microsoft dokumentiert das Verhalten von fflush(stdin). Anscheinend funktioniert es wie auf der Windows-Plattform dokumentiert, wobei der native Windows-Compiler und die C-Laufzeitunterstützungsbibliotheken verwendet werden.

Trotz gegenteiliger Dokumentation funktioniert es unter Linux nicht, wenn die Standardeingabe ein Terminal ist, aber es scheint der POSIX-Spezifikation zu folgen, die weitaus sorgfältiger formuliert ist. Nach dem C-Standard ist das Verhalten von fflush(stdin)undefiniert. POSIX fügt das Qualifikationsmerkmal "es sei denn, die Eingabedatei ist durchsuchbar" hinzu, was ein Terminal nicht ist. Das Verhalten ist nicht das gleiche wie bei Microsoft.

Folglich wird portabler Code nicht verwendet fflush(stdin). Code, der an die Microsoft-Plattform gebunden ist, wird möglicherweise verwendet und funktioniert. Beachten Sie jedoch die Portabilitätsprobleme.

POSIX-Methode zum Verwerfen ungelesener Terminaleingaben aus einem Dateideskriptor

Die POSIX Standardweg ungelesene Daten von einem Terminal Dateideskriptor zu verwerfen (wie dergleichen zu einem Dateistrom entgegengesetzt stdin) wird bei illustriert Wie kann ich flush ungelesene Daten von einer TTY - Eingabewarteschlange auf einem Unix - System . Dies funktioniert jedoch unterhalb der Standard-E / A-Bibliotheksebene.

Jonathan Leffler
quelle
Das ist wahrscheinlich die bessere Antwort im Zusammenhang mit dem, was das OP verlangt hat, obwohl die akzeptierte nicht falsch ist. Um klar zu zeigen, dass es auf der einen Seite nicht standardkonform ist, auf der anderen Seite jedoch, dass es für eine bestimmte Implementierung richtig verwendet werden kann. +1
RobertS unterstützt Monica Cellio
21

fflushKann laut Standard nur mit Ausgabepuffern verwendet werden und ist offensichtlich stdinkeiner. Doch einige bieten Standard - C - Bibliotheken die Verwendung fflush(stdin)als Erweiterung. In diesem Fall können Sie es verwenden, es beeinträchtigt jedoch die Portabilität, sodass Sie keine standardkonforme Standard-C-Bibliothek mehr auf der Erde verwenden können und dieselben Ergebnisse erwarten.

tiftik
quelle
8

Ich glaube, Sie sollten niemals anrufen fflush(stdin), aus dem einfachen Grund, dass Sie es niemals für notwendig halten sollten, Eingaben zu löschen. Realistisch gesehen gibt es nur einen Grund, von dem Sie vielleicht denken, dass Sie es müssen, und zwar: um schlechte Eingaben zu überwinden, scanfdie anhaften.

Zum Beispiel könnten Sie ein Programm , das in einer Schleife Lesen ganzen Zahlen mit sitzt scanf("%d", &n), und Sie haben entdeckt , dass das erste Mal der Benutzer ein nicht-stellige Zeichen wie 'x', das Programm in eine Endlosschleife geht .

In dieser Situation haben Sie meines Erachtens grundsätzlich drei Möglichkeiten:

  1. Flush die Eingabe irgendwie (wenn nicht mit fflush(stdin), dann durch Aufrufen getchar, um Zeichen zu lesen, bis \n, wie oft empfohlen).
  2. Weisen Sie den Benutzer an, keine nichtstelligen Zeichen einzugeben, wenn Ziffern erwartet werden.
  3. Verwenden Sie etwas anderes als scanfzum Lesen der Eingabe .

Wenn Sie ein Anfänger sind, scanf scheint dies der einfachste Weg zu sein, Eingaben zu lesen. Daher ist Wahl 3 beängstigend und schwierig. Aber # 2 scheint eine echte Ausrede zu sein, denn jeder weiß, dass benutzerunfreundliche Computerprogramme ein Problem sind, also wäre es schön, es besser zu machen. Allzu viele anfängliche Programmierer werden in eine Ecke gemalt und haben das Gefühl, keine andere Wahl zu haben, als # 1 zu tun. Sie müssen mehr oder weniger Eingaben mit verwenden scanf, was bedeutet, dass sie bei schlechten Eingaben hängen bleiben, was bedeutet, dass sie einen Weg finden müssen, um die schlechten Eingaben zu löschen, was bedeutet, dass sie sehr versucht sind, sie zu verwenden fflush(stdin).

Ich möchte alle beginnenden Programmierer ermutigen, andere Kompromisse einzugehen:

  1. Während der frühesten Stadien Ihrer C - Programmierung Karriere, bevor Sie bequem etwas anders als mit scanf, nur keine Sorge über schlechten Eingang . Ja wirklich. Fahren Sie fort und verwenden Sie Cop-out Nr. 2 oben. Stellen Sie sich das so vor: Sie sind Anfänger, es gibt viele Dinge, die Sie noch nicht wissen, und eines der Dinge, die Sie noch nicht wissen, ist: Gehen Sie mit unerwarteten Eingaben elegant um.

  2. Erfahren Sie so bald wie möglich, wie Sie Eingaben mit anderen Funktionen als ausführenscanf . An diesem Punkt können Sie anfangen, mit schlechten Eingaben elegant umzugehen, und Ihnen stehen viel mehr, viel bessere Techniken zur Verfügung, bei denen Sie nicht versuchen müssen, die schlechten Eingaben überhaupt zu löschen.

Mit anderen Worten, Anfänger, die noch nicht weiterkommen, scanfsollten Cop-out Nr. 2 verwenden können. Wenn sie bereit sind, sollten sie von dort zu Technik Nr. 3 übergehen, und niemand sollte Technik Nr. 1 verwenden, um dies zu versuchen Flush-Eingang überhaupt (und schon gar nicht mit fflush(stdin).

Steve Summit
quelle
Ein Punkt, den Sie nicht auswählen sollten, weil er meiner Meinung nach etwas mehrdeutig ist und jemand Sie möglicherweise falsch verstehen könnte: " Spülen Sie die Eingabe irgendwie (wenn nicht durch Verwenden fflush(stdin), dann durch Aufrufen getchar, um Zeichen zu lesen, bis \n, wie oft empfohlen). " - Ein Aufruf von getchar()Doesn lesen Charakter't s , bis er findet \n. Wenn mehrere Zeichen vorhanden sind, wird bei einem Aufruf von getchar()nur das zuletzt eingegebene Zeichen abgerufen, nicht alle bis und auch ohne die neue Zeile. Darüber getchar()hinaus kann auch eine Newline verbraucht werden.
RobertS unterstützt Monica Cellio
2

Das Spülen fflush(stdin)von Eingaben ähnelt dem Wünscheln von Wasser mit einem Stab in der Form des Buchstabens "S".

Und wenn man den Leuten hilft, Eingaben auf eine "bessere" Weise zu spülen, ist das so, als würde man zu einem S-Stick-Browser eilen und sagen: "Nein, nein, du machst es falsch, du musst einen Y-förmigen Stick verwenden!".

Mit anderen Worten, das eigentliche Problem ist nicht, dass fflush(stdin)es nicht funktioniert. Das Anrufen fflush(stdin)ist ein Symptom für ein zugrunde liegendes Problem. Warum müssen Sie Eingaben überhaupt "spülen"? Das ist dein Problem.

Und normalerweise besteht das zugrunde liegende Problem darin, dass Sie scanfin einem der vielen verwirrenden Modi unerwartet Zeilenumbrüche oder andere Leerzeichen in der Eingabe belassen. Die beste langfristige Antwort ist daher zu lernen, wie man Eingaben mit besseren Techniken als machtscanf .

Steve Summit
quelle
Obwohl ich denke, dass Sie diese Antwort vergessen haben stackoverflow.com/a/58884121/918959
Antti Haapala
@AnttiHaapala Danke für den Zeiger, aber nein, ich habe nicht vergessen; Beide Antworten sind in meinen Notizen zu diesem Thema verlinkt. Weitere gute, kanonische Antworten finden Sie unter stackoverflow.com/questions/34219549 .
Steve Summit
Ich meine, sie haben dieselbe Frage: D
Antti Haapala
@AnttiHaapala Ja, das verstehe ich. Als ich die zweite veröffentlichte, fragte mich SO: "Sie haben bereits eine Antwort auf diese Frage. Sind Sie sicher, dass Sie sie erneut beantworten möchten?", Und ich antwortete: "Ja." Für diese ewigen Fragen versuche ich immer, verschiedene / bessere / alternative Wege zu finden, um sie zu beantworten. (Ein weiteres Beispiel ist stackoverflow.com/questions/949433 .)
Steve Summit
1

Zitat aus POSIX :

Wenn sich ein zum Lesen geöffneter Stream noch nicht in EOF befindet und die Datei suchen kann, wird der Dateiversatz der zugrunde liegenden Beschreibung der geöffneten Datei auf die Dateiposition des Streams gesetzt und alle Zeichen zurückgeschoben Auf den Stream von ungetc () oder ungetwc (), die anschließend nicht aus dem Stream gelesen wurden, wird verworfen (ohne den Datei-Offset weiter zu ändern).

Beachten Sie, dass das Terminal nicht suchen kann.

Ryan Chen
quelle