Le comportement de printf()
semble dépendre de l'emplacement de stdout
.
stdout
est envoyé à la console, alors printf()
est tamponné en ligne et vidé après la nouvelle ligne.stdout
est redirigé vers un fichier, le tampon n'est pas vidé sauf si fflush()
est appelé.printf()
est utilisé avant que stdout
soit redirigé vers un fichier, les écritures ultérieures (dans le fichier) sont mises en mémoire tampon et sont vidées après une nouvelle ligne.Quand stdout
est-il mis en mémoire tampon en ligne et quand doit-on appeler fflush()
?
void RedirectStdout2File(const char* log_path) {
int fd = open(log_path, O_RDWR|O_APPEND|O_CREAT,S_IRWXU|S_IRWXG|S_IRWXO);
dup2(fd,STDOUT_FILENO);
if (fd != STDOUT_FILENO) close(fd);
}
int main_1(int argc, char* argv[]) {
/* Case 1: stdout is line-buffered when run from console */
printf("No redirect; printed immediately\n");
sleep(10);
}
int main_2a(int argc, char* argv[]) {
/* Case 2a: stdout is not line-buffered when redirected to file */
RedirectStdout2File(argv[0]);
printf("Will not go to file!\n");
RedirectStdout2File("/dev/null");
}
int main_2b(int argc, char* argv[]) {
/* Case 2b: flushing stdout does send output to file */
RedirectStdout2File(argv[0]);
printf("Will go to file if flushed\n");
fflush(stdout);
RedirectStdout2File("/dev/null");
}
int main_3(int argc, char* argv[]) {
/* Case 3: printf before redirect; printf is line-buffered after */
printf("Before redirect\n");
RedirectStdout2File(argv[0]);
printf("Does go to file!\n");
RedirectStdout2File("/dev/null");
}
Le vidage de stdout
est déterminé par son comportement de mise en mémoire tampon. La mise en mémoire tampon peut être définie sur trois modes: _IOFBF
(mise en mémoire tampon totale: attend jusqu'à ce que fflush()
soit possible), _IOLBF
(mise en mémoire tampon en ligne: newline déclenche le vidage automatique) et _IONBF
(écriture directe toujours utilisée). "La prise en charge de ces caractéristiques est définie par la mise en oeuvre et peut être affectée via les fonctions setbuf()
et setvbuf()
." [C99: 7.19.3.3]
"Au démarrage du programme, trois flux de texte sont prédéfinis et n'ont pas besoin d'être ouverts explicitement - entrée standard (pour lire une entrée conventionnelle), sortie standard (pour écrire Sortie conventionnelle) et erreur standard (pour Lors de l'ouverture initiale du fichier , le flux d'erreur standard n'est pas entièrement mis en mémoire tampon; les flux d'entrée standard et de sortie standard sont intégralement mis en mémoire tampon si et seulement si le flux peut ne pas être référencé à un appareil interactif. " [C99: 7.19.3.7]
Donc, ce qui se passe, c’est que l’implémentation fait quelque chose de spécifique à la plate-forme pour décider si stdout
sera tamponné ou non. Dans la plupart des implémentations de libc, ce test est effectué lors de la première utilisation du flux.
printf()
est vidée automatiquement.fflush()
, à moins que vous n'y écriviez des gabarits de données.printf()
, stdout a acquis le mode de mise en tampon de ligne. Lorsque nous remplaçons le fichier fd par un fichier, celui-ci est toujours mis en mémoire tampon, de sorte que les données sont vidées automatiquement.Chaque libc a la latitude nécessaire pour interpréter ces exigences, puisque C99 ne spécifie pas ce qu'est un "périphérique interactif", pas plus que l'entrée stdio de POSIX étend cette option (au-delà de la nécessité d'ouvrir stderr en lecture).
Glibc. Voir filedoalloc.c: L111 . Ici, nous utilisons stat()
pour tester si le fd est un tty et définissons le mode de mise en mémoire tampon en conséquence. (Ceci est appelé depuis fileops.c.) stdout
a initialement un buffer nul, et il est alloué lors de la première utilisation du flux en fonction des caractéristiques de fd 1.
BSD libc. Code très similaire, mais beaucoup plus propre à suivre! Voir cette ligne dans makebuf.c
Vous combinez à tort des fonctions tamponnées et non tamponnées IO. Une telle combinaison doit être faite avec beaucoup de soin surtout lorsque le code doit être portable. (et il est mauvais d'écrire du code non transférable ...)
Il est certainement préférable d'éviter de combiner IO tamponné et non tamponné sur le même descripteur de fichier.
Entrées/sorties tamponnées: fprintf()
, fopen()
, fclose()
, freopen()
...
E/S sans tampon: write()
, open()
, close()
, dup()
...
Lorsque vous utilisez dup2()
pour rediriger stdout. La fonction n'a pas connaissance du tampon qui a été rempli par fprintf()
. Ainsi, lorsque dup2()
ferme l'ancien descripteur 1, il ne vide pas le tampon et le contenu peut être vidé sur une autre sortie. Dans votre cas 2a, il a été envoyé à /dev/null
.
Dans votre cas, il vaut mieux utiliser freopen()
au lieu de dup2()
. Cela résout tous vos problèmes:
FILE
d'origine. (cas 2a)Voici la mise en œuvre correcte de votre fonction:
void RedirectStdout2File(const char* log_path) {
if(freopen(log_path, "a+", stdout) == NULL) err(EXIT_FAILURE, NULL);
}
Malheureusement, avec la mémoire tampon IO, vous ne pouvez pas définir directement les autorisations d'un fichier nouvellement créé. Vous devez utiliser d'autres appels pour modifier les autorisations ou vous pouvez utiliser des extensions glibc non transférables. Voir la fopen() man page
.
Vous ne devez pas fermer le descripteur de fichier. Supprimez donc close(fd)
et fermez
stdout_bak_fd
si vous souhaitez que le message soit imprimé uniquement dans le fichier.