it-swarm.com.de

Verstehen der Notwendigkeit von fflush () und damit verbundenen Problemen

Nachstehend finden Sie Beispielcode für die Verwendung von fflush ():

#include <string.h>
#include <stdio.h>
#include <conio.h>
#include <io.h>

void flush(FILE *stream);

int main(void)
{
   FILE *stream;
   char msg[] = "This is a test";

   /* create a file */
   stream = fopen("DUMMY.FIL", "w");

   /* write some data to the file */
   fwrite(msg, strlen(msg), 1, stream);

   clrscr();
   printf("Press any key to flush DUMMY.FIL:");
   getch();

   /* flush the data to DUMMY.FIL without closing it */
   flush(stream);

   printf("\nFile was flushed, Press any key to quit:");
   getch();
   return 0;
}

void flush(FILE *stream)
{
     int duphandle;

     /* flush the stream's internal buffer */
     fflush(stream);

     /* make a duplicate file handle */
     duphandle = dup(fileno(stream));

     /* close the duplicate handle to flush the DOS buffer */
     close(duphandle);
}

Alles, was ich über fflush () weiß, ist, dass es sich um eine Bibliotheksfunktion handelt, mit der ein Ausgabepuffer geleert wird. Ich möchte wissen, was der Hauptzweck von fflush () ist und wo kann ich es verwenden. Und vor allem interessiere ich mich für welche Probleme kann es mit fflush () geben.

12
Karan Mer

Es ist etwas schwer zu sagen, was "Probleme mit" (übermäßiger?) Verwendung von fflushsein kann. Alle Arten von Dingen können können je nach Ihren Zielen und Ansätzen Probleme sein oder werden. Eine bessere Sichtweise ist wahrscheinlich die Absicht von fflushname__.

Zunächst ist zu beachten, dass fflushnur für Ausgabeströme definiert ist. Ein Ausgabestrom sammelt "Dinge, die in eine Datei geschrieben werden sollen" in einem großen Puffer (ish) und schreibt diesen Puffer dann in die Datei. Bei diesem Sammeln und Schreiben geht es darum, die Geschwindigkeit/Effizienz auf zwei Arten zu verbessern:

  • In modernen Betriebssystemen gibt es einige Strafen für das Überschreiten der Benutzer-/Kernel-Schutzgrenze (das System muss einige Schutzinformationen in der CPU ändern usw.). Wenn Sie eine große Anzahl von Schreibaufrufen auf Betriebssystemebene durchführen, zahlen Sie diese Strafe für jeden Anruf. Wenn Sie etwa 8192 einzelne Schreibvorgänge in einem großen Puffer sammeln und dann einen Aufruf tätigen, wird der Großteil des Overheads entfernt.
  • Bei vielen modernen Betriebssystemen versucht jeder Schreibaufruf des Betriebssystems, die Dateileistung in gewisser Weise zu optimieren, z. B. indem Sie feststellen, dass Sie eine kurze Datei um eine längere erweitert haben, und es ist sinnvoll, den Plattenblock von Punkt A an zu verschieben Die Platte zeigt auf Punkt B auf der Festplatte, damit die längeren Daten zusammenhängend passen. (Bei älteren Betriebssystemen ist dies ein separater "Defragmentierungsschritt", den Sie manuell ausführen können. Sie können sich das als modernes Betriebssystem vorstellen, das dynamische, sofortige Defragmentierung durchführt.) Wenn Sie beispielsweise 500 Byte schreiben würden, und dann 700 und so weiter, wird es eine Menge Arbeit tun; Wenn Sie jedoch einen großen Aufruf mit beispielsweise 8192 Byte tätigen, kann das Betriebssystem einen großen Block einmal zuweisen und alles dort ablegen, ohne dass Sie später erneut defragmentieren müssen.

Also, die Leute, die Ihre C-Bibliothek und ihre stdio-Stream-Implementierung bereitstellen, tun alles, was in Ihrem Betriebssystem angemessen ist, um eine "einigermaßen optimale" Blockgröße zu finden und die gesamte Ausgabe in einem Block dieser Größe zusammenzufassen. (Die Zahlen 4096, 8192, 16384 und 65536 sind heutzutage oft gut, aber es hängt wirklich vom Betriebssystem und manchmal auch vom zugrunde liegenden Dateisystem ab. Beachten Sie, dass "größer" nicht immer "besser" ist: Das Streaming von Daten in Blöcken von jeweils vier Gigabyte führt möglicherweise zu einer schlechteren Leistung als beispielsweise in 64-KB-Blöcken.)

Dies schafft jedoch ein Problem. Angenommen, Sie schreiben in eine Datei, z. B. eine Protokolldatei mit Datums- und Zeitstempeln und Meldungen, und Ihr Code wird später weiter in diese Datei schreiben. Im Moment möchte er jedoch eine Zeitlang unterbrechen und dies zulassen Ein Log-Analyzer liest den aktuellen Inhalt der Log-Datei. Eine Option ist, fclosezum Schließen der Protokolldatei zu verwenden, und dann fopenname__, um sie erneut zu öffnen, um später weitere Daten anzufügen. Es ist jedoch effizienter, ausstehende Protokollmeldungen in die zugrunde liegende Betriebssystemdatei zu verschieben, die Datei jedoch geöffnet zu lassen. Das ist, was fflushtut.

Pufferung führt auch zu einem anderen Problem. Angenommen, Ihr Code enthält einen Fehler und stürzt manchmal ab, Sie sind sich jedoch nicht sicher, ob er abstürzt. Angenommen, Sie haben etwas geschrieben und es ist sehr wichtig, dass dies Daten in das darunterliegende Dateisystem gelangen. Sie können fflushaufrufen, um die Daten an das Betriebssystem weiterzuleiten, bevor Sie den möglicherweise fehlerhaften Code aufrufen, der abstürzt. (Manchmal ist das gut für das Debuggen.)

Oder nehmen Sie an, Sie befinden sich auf einem Unix-ähnlichen System und haben einen Systemaufruf forkname__. Dieser Aufruf dupliziert den gesamten Benutzerbereich (macht einen Klon des ursprünglichen Prozesses). Die stdio-Puffer befinden sich im Benutzerbereich, so dass der Klon zum Zeitpunkt des Aufrufs forkdie gleichen gepufferten, aber noch nicht geschriebenen Daten hat, die der ursprüngliche Prozess hatte. Auch hier besteht eine Möglichkeit, das Problem zu lösen, indem Sie fflushverwenden, um gepufferte Daten auszulagern, bevor Sie forkausführen. Wenn vor dem forkalles raus ist, gibt es nichts zu duplizieren; Der frische Klon wird nicht versuchen, die gepufferten Daten zu schreiben, da diese nicht mehr vorhanden sind.

Je mehr fflushname __- es hinzugefügt werden, desto mehr schlagen Sie die ursprüngliche Idee des Sammelns großer Datenblöcke zurück. Das heißt, Sie machen einen Kompromiss: Große Brocken sind effizienter, verursachen jedoch ein anderes Problem, also treffen Sie die Entscheidung: "Seien Sie hier weniger effizient, um ein Problem zu lösen, das wichtiger ist als bloße Effizienz". Sie rufen fflushan.

Manchmal ist das Problem einfach "Debuggen der Software". In diesem Fall können Sie, anstatt wiederholt fflushaufzurufen, Funktionen wie setbufund setvbufverwenden, um das Pufferverhalten eines stdio-Streams zu ändern. Dies ist bequemer (weniger oder gar keine Codeänderungen erforderlich - Sie können den set-buffering-Aufruf mit einem Flag steuern), als viele fflushname__-Aufrufe hinzuzufügen, so dass dies als "Problem bei der Verwendung (oder übermäßiger Verwendung)" angesehen werden kann ) von fflush".

43
torek

Nun, @ toreks Antwort ist fast perfekt, aber es gibt einen Punkt, der nicht so genau ist. 

Das erste, was zu beachten ist, ist, dass fflush nur für Ausgabe Streams definiert wird.

Laut man fflush kann fflush auch in input streams verwendet werden: 

Für Ausgabeströme erzwingt fflush () das Schreiben aller im [...] gepufferten Daten des Benutzerbereichs Für den angegebenen Ausgabe- oder Aktualisierungsstrom über die Des Streams zugrunde liegende Schreibfunktion. Für Eingabeströme verwirft fflush () alle gepufferten Daten, die aus der zugrunde liegenden Datei abgerufen wurden, aber nicht von Der Anwendung verarbeitet wurden. Der offene Status von Des Streams bleibt davon unberührt. Wenn er bei der Eingabe verwendet wird, verwerfen Sie ihn mit fflush. 

Hier ist eine Demo, um es zu veranschaulichen:

#include<stdio.h>

#define MAXLINE 1024

int main(void) {
  char buf[MAXLINE];

  printf("Prompt: ");
  while (fgets(buf, MAXLINE, stdin) != NULL)
    fflush(stdin);
    if (fputs(buf, stdout) == EOF)
      printf("output err");

  exit(0);
}
1
jaseywang