web-dev-qa-db-fra.com

Pourquoi printf ne videra-t-il pas après l'appel sauf si une nouvelle ligne est dans la chaîne de format?

Pourquoi printf ne disparaît-il pas après l'appel sauf si une nouvelle ligne est dans la chaîne de format? Est-ce que ce comportement POSIX? Comment pourrais-je avoir printf immédiatement à chaque fois?

495
Crazy Chenz

Le flux stdout est une ligne mise en mémoire tampon par défaut. Par conséquent, le contenu de la mémoire tampon ne sera affiché que lorsqu'il aura atteint une nouvelle ligne (ou quand il le sera demandé). Vous avez quelques options pour imprimer immédiatement:

Imprimer dans stderrinstead à l'aide de fprintf (stderr est non mis en mémoire tampon par défaut ):

fprintf(stderr, "I will be printed immediately");

Rincez la sortie standard chaque fois que vous en avez besoin d'utiliser fflush:

printf("Buffered, will be flushed");
fflush(stdout); // Will now print everything in the stdout buffer

Edit : D'après le commentaire d'Andy Ross ci-dessous, vous pouvez également désactiver la mise en mémoire tampon sur la sortie standard en utilisant setbuf:

setbuf(stdout, NULL);
649
Rudd Zwolinski

Non, ce n'est pas un comportement POSIX, c'est un comportement ISO (enfin, il est un comportement POSIX mais uniquement dans la mesure où ils sont conformes à ISO).

La sortie standard est mise en mémoire tampon de ligne si elle peut être détectée pour faire référence à un périphérique interactif, sinon elle est entièrement mise en mémoire tampon. Il existe donc des situations dans lesquelles printf ne videra pas, même si une nouvelle ligne est envoyée, telle que:

myprog >myfile.txt

Cela a du sens pour plus d'efficacité car, si vous interagissez avec un utilisateur, celui-ci veut probablement voir chaque ligne. Si vous envoyez la sortie dans un fichier, il est fort probable qu'il n'y ait pas d'utilisateur à l'autre bout (bien que ce ne soit pas impossible, il se peut que le fichier se termine). Maintenant, vous pourriez prétendre que l'utilisateur veut voir tous les caractères, mais cela pose deux problèmes.

La première est que ce n'est pas très efficace. La seconde est que le mandat initial de l’ANSI C consistait principalement à codifier le comportement existant plutôt qu’à inventer un nouveau comportement, et ces décisions de conception ont été prises bien avant que l'ANSI ne lance le processus. De nos jours, même ISO modifie avec précaution les règles existantes dans les normes.

Pour résoudre ce problème, si vous fflush (stdout) après chaque appel de sortie que vous souhaitez voir immédiatement, cela résoudra le problème.

Vous pouvez également utiliser setvbuf avant d'utiliser stdout, pour le définir sur Non tampon. Vous n'aurez pas à vous soucier de l'ajout de toutes ces lignes fflush à votre code:

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

Gardez simplement à l'esprit que cela peut affecter les performances si vous envoyez la sortie dans un fichier. N'oubliez pas non plus que la prise en charge de cette opération est définie par l'implémentation et n'est pas garantie par la norme.

La section ISO C99 7.19.3/3 est le bit pertinent:

Lorsqu'un flux est non mis en mémoire tampon , les caractères doivent apparaître dès que possible dans la source ou à la destination. Sinon, les caractères peuvent être accumulés et transmis vers ou depuis l'environnement hôte sous forme de bloc.

Lorsqu'un flux est entièrement mis en mémoire tampon , les caractères sont destinés à être transmis vers ou depuis l'environnement hôte sous forme de bloc lorsqu'un tampon est rempli.

Lorsqu'un flux est en ligne , les caractères sont destinés à être transmis à l'environnement hôte ou à partir de celui-ci en tant que bloc lorsqu'un caractère de nouvelle ligne est rencontré.

De plus, les caractères sont destinés à être transmis sous forme de bloc à l'environnement hôte lorsqu'un tampon est rempli, lorsque l'entrée est demandée sur un flux non tamponné ou lorsque l'entrée est demandée sur un flux en ligne mis en mémoire tampon qui nécessite la transmission de caractères de l'environnement hôte. .

La prise en charge de ces caractéristiques est définie par l'implémentation et peut être affectée via les fonctions setbuf et setvbuf.

120
paxdiablo

C’est probablement ce qui se produit pour des raisons d’efficacité et parce que, si vous avez plusieurs programmes écrivant sur un même ATS, vous éviterez ainsi que les caractères ne soient entrelacés. Donc, si les programmes A et B sont en sortie, vous aurez généralement:

program A output
program B output
program B output
program A output
program B output

Cela pue, mais c'est mieux que

proprogrgraam m AB  ououtputputt
prproogrgram amB A  ououtputtput
program B output

Notez qu'il n'est même pas garanti de vider une nouvelle ligne, vous devriez donc vider explicitement si vider est important.

28

Pour vider immédiatement l'appel fflush(stdout) ou fflush(NULL) (NULL signifie tout vider).

24
Aaron

Remarque: les bibliothèques d'exécution Microsoft ne prennent pas en charge la mise en mémoire tampon des lignes, donc printf("will print immediatelly to terminal"):

http://msdn.Microsoft.com/en-us/library/86cebhfs.aspx

15
Renato

stdout est mis en mémoire tampon, il ne sortira donc qu'après l'impression d'une nouvelle ligne.

Pour obtenir une sortie immédiate, soit:

  1. Imprimez sur stderr.
  2. Assurez stdout sans tampon.
11
Douglas Leeder

par défaut, stdout est une ligne mise en mémoire tampon, stderr n'est pas mis en mémoire tampon et le fichier est complètement mis en mémoire tampon.

11
woso

Vous pouvez utiliser fprintf sur stderr, qui n'est pas mis en mémoire tampon, à la place. Ou vous pouvez vider stdout quand vous voulez. Ou vous pouvez définir stdout sur non tamponné.

10
Rasmus Kaj

Utilisez setbuf(stdout, NULL); pour désactiver la mise en mémoire tampon.

9
dnahc araknayirp

Il y a généralement 2 niveaux de buffering-

1. Cache du noyau (cache) (accélère la lecture/écriture)

2. Mise en mémoire tampon dans la bibliothèque d'E/S (réduit le nombre d'appels système)

Prenons l'exemple de fprintf and write().

Lorsque vous appelez fprintf(), il n'est pas directement connecté au fichier. Il va d'abord dans la mémoire tampon stdio dans la mémoire du programme. À partir de là, il est écrit dans la mémoire cache du noyau à l'aide de l'appel système d'écriture. Donc, un moyen d’ignorer le tampon d’E/S consiste à utiliser directement write (). Vous pouvez également utiliser setbuff(stream,NULL). Ceci définit le mode de mise en mémoire tampon sur Aucune et les données sont directement écrites dans la mémoire tampon du noyau. Pour forcer le décalage des données dans la mémoire tampon du noyau, nous pouvons utiliser "\ n", qui, en cas de mode de mise en mémoire tampon par défaut de 'ligne mise en mémoire tampon', videra la mémoire tampon d'E/S. Ou nous pouvons utiliser fflush(FILE *stream).

Nous sommes maintenant dans la mémoire tampon du noyau. Le noyau (/ OS) veut minimiser le temps d'accès au disque et par conséquent, il ne lit/écrit que des blocs de disque. Ainsi, lorsqu'un read() est émis, ce qui est un appel système et peut être appelé directement ou par le biais de fscanf(), le noyau lit le bloc de disque à partir du disque et le stocke dans un tampon. Une fois que les données sont copiées d’ici vers l’espace utilisateur.

De même, les données fprintf() reçues du tampon d'E/S sont écrites sur le disque par le noyau. Cela rend read () write () plus rapide.

Maintenant, pour forcer le noyau à lancer une write(), après laquelle le transfert de données est contrôlé par des contrôleurs matériels, il y a aussi plusieurs façons. Nous pouvons utiliser O_SYNC ou des indicateurs similaires pendant les appels en écriture. Ou nous pourrions utiliser d'autres fonctions telles que fsync(),fdatasync(),sync() pour que le noyau commence à écrire dès que des données sont disponibles dans la mémoire tampon du noyau.

1
o_O