web-dev-qa-db-fra.com

Comprendre la nécessité de fflush () et les problèmes qui y sont associés

Vous trouverez ci-dessous un exemple de code d'utilisation de 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);
}

Tout ce que je sais sur fflush (), c'est qu'il s'agit d'une fonction de bibliothèque utilisée pour vider un tampon de sortie. Je veux savoir quel est l'objectif principal d'utiliser fflush () et où puis-je l'utiliser. Et surtout, je voudrais savoir quels problèmes l’utilisation de fflush () peut poser des problèmes.

12
Karan Mer

Il est un peu difficile de dire quel "peut être un problème avec" l'utilisation (excessive?) De fflush. Toutes sortes de choses peuvent être, ou devenir, des problèmes, en fonction de vos objectifs et de vos approches. L’intention de fflush est probablement une meilleure façon d’examiner cela.

La première chose à considérer est que fflush est défini uniquement sur les flux de sortie. Un flux de sortie collecte les "éléments à écrire dans un fichier" dans un grand tampon (ish), puis écrit ce tampon dans le fichier. Le but de cette collecte-écriture ultérieure est d'améliorer la vitesse/efficacité de deux manières:

  • Sur les systèmes d’exploitation modernes, le franchissement de la limite de protection utilisateur/noyau est pénalisé (le système doit modifier certaines informations de protection dans la CPU, etc.). Si vous effectuez un grand nombre d'appels en écriture au niveau du système d'exploitation, vous payez cette pénalité pour chacun d'entre eux. Si vous récupérez, disons, environ 8192 écritures individuelles dans une mémoire tampon volumineuse, puis effectuez un appel, vous supprimez la majeure partie de cette surcharge.
  • Sur de nombreux systèmes d’exploitation modernes, chaque appel d’écriture de système d’exploitation tente d’optimiser les performances des fichiers, par exemple en découvrant que vous avez étendu un fichier court à un fichier plus long et qu’il serait bien de déplacer le bloc de disque du point A sur le disque à pointer B sur le disque, de sorte que les données les plus longues puissent tenir de manière contiguë. (Sur les anciens systèmes d’exploitation, il s’agit d’une étape distincte de «défragmentation» que vous pouvez exécuter manuellement. Vous pouvez considérer cela comme un système d’exploitation moderne effectuant une défragmentation dynamique et instantanée.) Si vous deviez écrire 500 octets, puis 200 autres, et puis 700, et ainsi de suite, cela fera beaucoup de ce travail; mais si vous passez un gros appel avec, par exemple, 8 192 octets, le système d’exploitation peut allouer une fois un gros bloc, y mettre tout et ne pas avoir à se défragmenter plus tard.

Ainsi, les personnes qui fournissent votre bibliothèque C et son implémentation de flux stdio font tout ce qui est approprié sur votre système d'exploitation pour trouver une taille de bloc "raisonnablement optimale" et pour rassembler toutes les sorties dans un bloc de cette taille. (Les numéros 4096, 8192, 16384 et 65536 ont souvent tendance à être bons de nos jours, mais cela dépend vraiment du système d'exploitation et parfois du système de fichiers sous-jacent. Notez que "plus gros" n'est pas toujours "meilleur": la diffusion de données en morceaux de quatre gigaoctets à la fois sera probablement moins efficace que de le faire en morceaux de 64 Ko, par exemple.)

Mais cela crée un problème. Supposons que vous écriviez dans un fichier, tel qu'un fichier journal avec des horodatages et des messages de date et d'heure, et que votre code continue d'écrire plus tard dans ce fichier, mais qu'il souhaite à présent suspendre pendant un certain temps et laisser Un analyseur de journaux lit le contenu actuel du fichier journal. Une option consiste à utiliser fclose pour fermer le fichier journal, puis fopen pour l'ouvrir à nouveau afin d'ajouter plus de données ultérieurement. Il est cependant plus efficace de transférer tous les messages de journal en attente dans le fichier du système d'exploitation sous-jacent, tout en maintenant le fichier ouvert. C'est ce que fflush fait.

La mise en mémoire tampon crée également un autre problème. Supposons que votre code comporte un bogue et qu’il se bloque parfois, mais vous ne savez pas s’il est sur le point de planter. Et supposons que vous ayez écrit quelque chose et qu’il est très important que les ceci données soient transmises au système de fichiers sous-jacent. Vous pouvez appeler fflush pour transmettre les données au système d’exploitation avant d’appeler votre code potentiellement défectueux susceptible de se bloquer. (Parfois, c'est bon pour le débogage.)

Ou bien, supposons que vous êtes sur un système de type Unix et que vous avez un appel système fork. Cet appel duplique tout l'espace utilisateur (crée un clone du processus d'origine). Les mémoires tampon stdio sont dans l'espace utilisateur, de sorte que le clone a les mêmes données mises en mémoire tampon mais non encore écrites que le processus original, au moment de l'appel fork. Ici encore, une solution au problème consiste à utiliser fflush pour extraire les données en mémoire tampon juste avant de procéder à la fork. Si tout est sorti avant la fork, il n'y a rien à dupliquer; le nouveau clone ne tentera jamais d'écrire les données mises en mémoire tampon, car elles n'existent plus.

Plus vous ajoutez fflush- variables, plus vous rejetez l'idée originale de collecte de gros morceaux de données. En d'autres termes, vous faites un compromis: les gros morceaux sont plus efficaces, mais causent un autre problème, vous prenez donc la décision: "soyez moins efficace ici, pour résoudre un problème plus important que la simple efficacité". Vous appelez fflush.

Parfois, le problème est simplement "déboguer le logiciel". Dans ce cas, au lieu d'appeler à plusieurs reprises fflush, vous pouvez utiliser des fonctions telles que setbuf et setvbuf pour modifier le comportement de mise en mémoire tampon d'un flux stdio. C’est plus pratique (moins de modifications, voire aucune, de modifications de code nécessaires (vous pouvez contrôler l’appel de mise en tampon avec un indicateur) plutôt que d’ajouter de nombreux appels fflush, ce qui pourrait être considéré comme un "problème lié à ) de fflush ".

43
torek

Eh bien, la réponse de @ torek est presque parfaite, mais il y a un point qui n'est pas aussi précis. 

La première chose à considérer est que fflush est défini uniquement sur la sortie ruisseaux.

Selon man fflush, fflush peut également être utilisé dans input streams: 

Pour les flux de sortie, fflush () force l'écriture de tout l'espace utilisateur données en mémoire tampon pour le flux de sortie ou de mise à jour donné via le flux du fichier fonction d'écriture sous-jacente. Pour flux d’entrée, fflush () ignore toutes les données mises en mémoire tampon extraites du fichier sous-jacent, mais n’ayant pas été utilisées par. L'application . Le statut ouvert de le flux n'est pas affecté . Donc, lorsqu'il est utilisé en entrée, fflush suffit de le supprimer. 

Voici une démo pour illustrer cela:

#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