Pourquoi printf ne vide-t-il pas après l'appel à moins qu'une nouvelle ligne ne soit dans la chaîne de format?


539

Pourquoi ne printfvide- t-il pas après l'appel à moins qu'un saut de ligne ne soit dans la chaîne de format? S'agit-il d'un comportement POSIX? Comment ai-je pu printfrincer immédiatement à chaque fois?


2
avez-vous cherché à savoir si cela se produit avec n'importe quel fichier ou uniquement avec des terminaux? qui sonnerait comme une caractéristique de terminal intelligent pour ne pas la ligne de sortie inachevés d'un programme d'arrière - plan, bien que je pense qu'il ne serait pas applicable à la programme de premier plan.
PypeBros

7
Sous Cygwin bash, je vois ce même mauvais comportement même si une nouvelle ligne est dans la chaîne de format. Ce problème est nouveau pour Windows 7; le même code source a bien fonctionné sur Windows XP. MS cmd.exe se vide comme prévu. Le correctif setvbuf(stdout, (char*)NULL, _IONBF, 0)résout le problème, mais n'aurait certainement pas dû être nécessaire. J'utilise MSVC ++ 2008 Express. ~~~
Steve Pitchers

9
Pour clarifier le titre de la question: printf(..) ne fait pas de vidage lui-même, c'est le tampon de stdoutcelui-ci qui peut vider en voyant une nouvelle ligne (si elle est mise en mémoire tampon de ligne). Il réagirait de la même manière putchar('\n');, ce printf(..)n'est donc pas spécial à cet égard. Ceci est en contraste avec cout << endl;, dont la documentation mentionne en bonne place le rinçage. La documentation de printf ne mentionne pas du tout le rinçage.
Evgeni Sergeev

1
l'écriture (/ flushing) est potentiellement une opération coûteuse, elle est probablement mise en mémoire tampon pour des raisons de performances.
hanshenrik

@EvgeniSergeev: Existe-t-il un consensus sur le fait que la question a mal diagnostiqué le problème et que le vidage se produit lorsqu'il y a une nouvelle ligne dans la sortie ? (en mettre un dans la chaîne de format est un moyen, mais pas le seul, d'en obtenir un dans la sortie).
Ben Voigt

Réponses:


703

Le stdoutflux est mis en mémoire tampon par défaut, donc n'affichera ce qui se trouve dans le tampon qu'après avoir atteint une nouvelle ligne (ou quand cela lui est demandé). Vous avez quelques options pour imprimer immédiatement:

Imprimer à la stderrplace à l' aide fprintf( stderrest unbuffered par défaut ):

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

Rincez la sortie standard lorsque vous en avez besoin pour 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 stdout en utilisant setbuf:

setbuf(stdout, NULL);

ou sa version sécurisée setvbufcomme expliqué ici

setvbuf(stdout, NULL, _IONBF, 0); 

266
Ou, pour désactiver complètement la mise en mémoire tampon:setbuf(stdout, NULL);
Andy Ross

80
Aussi, je voulais juste mentionner que, apparemment sous UNIX, une nouvelle ligne ne videra généralement le tampon que si stdout est un terminal. Si la sortie est redirigée vers un fichier, une nouvelle ligne ne sera pas vidée.
hora

5
Je pense que je devrais ajouter: je viens de tester cette théorie, et je trouve que l'utilisation setlinebuf()sur un flux qui n'est pas dirigé vers un terminal est en train de vider à la fin de chaque ligne.
Doddy

8
"Tel qu'initialement ouvert, le flux d'erreur standard n'est pas entièrement tamponné; les flux d'entrée et de sortie standard sont entièrement tamponnés si et seulement si le flux peut être déterminé comme ne faisant pas référence à un périphérique interactif" - voir cette question: stackoverflow.com / questions / 5229096 /…
Seppo Enarvi

3
@RuddZwolinski Si cela doit être une bonne réponse canon de "pourquoi n'imprime-t-il pas", il semble important de mentionner la distinction terminal / fichier selon "Est-ce que printf vide toujours le tampon en rencontrant une nouvelle ligne?" directement dans cette réponse très appréciée, contre les personnes ayant besoin de lire les commentaires ...
HostileFork dit de ne pas faire confiance au SE

128

Non, ce n'est pas le comportement POSIX, son comportement ISO (bien, il est un comportement , mais seulement dans la mesure Posix où elles sont conformes à la norme 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 appareil interactif, sinon elle est entièrement mise en mémoire tampon. Il y a donc des situations printfqui ne seront pas vidées, même si une nouvelle ligne est envoyée, comme:

myprog >myfile.txt

Cela est logique pour l'efficacité car, si vous interagissez avec un utilisateur, il souhaite probablement voir chaque ligne. Si vous envoyez la sortie vers un fichier, il est très probable qu'il n'y ait pas d'utilisateur à l'autre extrémité (bien que ce ne soit pas impossible, ils pourraient être en train de suivre le fichier). Maintenant, vous pourriez dire que l'utilisateur veut voir chaque personnage, mais cela pose deux problèmes.

Le premier est qu'il n'est pas très efficace. La seconde est que le mandat original de l'ANSI C était de codifier principalement les comportements existants , plutôt que d'inventer de nouveaux comportements, et ces décisions de conception ont été prises bien avant que l'ANSI ne commence le processus. De nos jours, même l'ISO procède avec beaucoup de prudence lors de la modification des règles existantes dans les normes.

Quant à la façon de gérer cela, si vous fflush (stdout)après chaque appel de sortie que vous souhaitez voir immédiatement, cela résoudra le problème.

Alternativement, vous pouvez utiliser setvbufavant d'opérer stdout, pour le mettre en mémoire tampon et vous n'aurez pas à vous soucier d'ajouter toutes ces fflushlignes à votre code:

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

Il suffit de garder à l' esprit qui peut affecter les performances un peu si vous êtes envoyer la sortie vers un fichier. Gardez également à l'esprit que la prise en charge de ceci est définie par l'implémentation, non garantie par la norme.

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

Lorsqu'un flux n'est pas mis en mémoire tampon , les caractères sont censés apparaître à partir de la source ou à destination dès que possible. 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 tamponné , 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 mis en mémoire tampon de ligne , les caractères sont destinés à être transmis vers ou depuis l'environnement hôte 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, lorsqu'une entrée est demandée sur un flux non tamponné ou lorsque l'entrée est demandée sur un flux tamponné de ligne 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 setbufet setvbuf.


8
Je viens de tomber sur un scénario où même il y a un '\ n', printf () ne se vide pas. Il a été surmonté en ajoutant un fflush (stdout), comme vous l'avez mentionné ici. Mais je me demande pourquoi "\ n" n'a pas pu vider le tampon dans printf ().
Qiang Xu

11
@QiangXu, la sortie standard est mise en mémoire tampon de ligne uniquement dans le cas où il peut être définitivement déterminé qu'elle fait référence à un appareil interactif. Ainsi, par exemple, si vous redirigez la sortie avec myprog >/tmp/tmpfile, celle-ci est entièrement mise en mémoire tampon plutôt que ligne mise en mémoire tampon. De la mémoire, la décision de savoir si votre sortie standard est interactive est laissée à l'implémentation.
paxdiablo

3
en outre, sur Windows, l'appel à setvbuf (...., _IOLBF) ne fonctionnera pas car _IOLBF est identique à _IOFBF: msdn.microsoft.com/en-us/library/86cebhfs.aspx
Piotr Lopusiewicz

28

C'est probablement comme ça à cause de l'efficacité et parce que si vous avez plusieurs programmes écrivant sur un seul ATS, de cette façon, vous n'obtenez pas de caractères sur une ligne entrelacée. Donc, si les programmes A et B sortent, vous obtiendrez généralement:

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

Ça 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 sur une nouvelle ligne, vous devez donc vider explicitement si le rinçage vous intéresse.


26

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


31
Gardez à l'esprit fflush(NULL);est généralement une très mauvaise idée. Cela tuera les performances si vous avez de nombreux fichiers ouverts, en particulier dans un environnement multi-thread où vous vous battez avec tout pour les verrous.
R .. GitHub STOP HELPING ICE

14

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

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setvbuf


3
Pire que d' printfaller immédiatement au terminal dans le cas «normal» est le fait que printfet fprintfobtenir un tampon plus grossier même dans les cas où leur sortie est mise à profit immédiatement. Sauf si MS a corrigé des problèmes, il est impossible pour un programme de capturer stderr et stdout d'un autre et d'identifier dans quel ordre les éléments ont été envoyés à chacun.
supercat

non, il ne l'imprime pas immédiatement sur le terminal, sauf si aucune mise en mémoire tampon n'a été définie. Par défaut, la mise en mémoire tampon complète est utilisée
phuclv

12

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

Pour obtenir une sortie immédiate, soit:

  1. Imprimer sur stderr.
  2. Faites stdout sans tampon.

10
Ou fflush(stdout).
RastaJedi

2
"ainsi sortira seulement après qu'une nouvelle ligne soit imprimée." Non seulement cela, mais au moins 4 autres cas. buffer full, write to stderr(cette réponse le mentionne plus tard) fflush(stdout),, fflush(NULL).
chux

11

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


10

Vous pouvez à la place fprintf vers stderr, qui est sans tampon. Ou vous pouvez vider la sortie standard lorsque vous le souhaitez. Ou vous pouvez définir stdout sur non tamponné.



2

Il existe généralement 2 niveaux de mise en mémoire tampon -

1. Cache du tampon du noyau (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 ne se connecte pas directement au fichier. Il va d'abord au tampon stdio dans la mémoire du programme. De là, il est écrit dans le cache du tampon du noyau à l'aide de l'appel système d'écriture. Donc, une façon d'ignorer le tampon d'E / S est d'utiliser directement write (). D'autres moyens sont en utilisant setbuff(stream,NULL). Cela définit le mode de mise en mémoire tampon sur aucune mise en mémoire tampon et les données sont directement écrites dans le tampon du noyau. Pour forcer les données à être déplacées vers le tampon du noyau, nous pouvons utiliser "\ n" qui, en cas de mode de tampon par défaut de "tampon de ligne", videra le tampon d'E / S. Ou nous pouvons utiliser fflush(FILE *stream).

Maintenant, nous sommes dans le tampon du noyau. Le noyau (/ OS) veut minimiser le temps d'accès au disque et donc il ne lit / écrit que des blocs de disque. Ainsi, quand un read()est émis, qui est un appel système et peut être appelé directement ou via fscanf(), le noyau lit le bloc de disque à partir du disque et le stocke dans un tampon. Une fois ces données copiées d'ici dans l'espace utilisateur.

De même, les fprintf()données 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 un write(), après quoi le transfert de données est contrôlé par des contrôleurs matériels, il existe également quelques moyens. Nous pouvons utiliser O_SYNCou des indicateurs similaires lors des appels d'écriture. Ou nous pourrions utiliser d'autres fonctions comme fsync(),fdatasync(),sync()pour faire lancer des écritures au noyau dès que les données sont disponibles dans le tampon du noyau.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.