2017-05-17 7 views
4

Je me demande pourquoi décommentant cette première printf dans le programme suivant modifie son comportement ultérieur:stdio à la borne après la fermeture (STDOUT_FILENO) comportement

#include <unistd.h> 
#include <stdio.h> 
#include <string.h> 
#include <errno.h> 

int main() { 
    //printf("hi from C \n"); 

    // Close underlying file descriptor: 
    close(STDOUT_FILENO); 

    if (write(STDOUT_FILENO, "Direct write\n", 13) != 13) // immediate error detected. 
    fprintf(stderr, "Error on write after close(STDOUT_FILENO): %s\n", strerror(errno)); 

    // printf() calls continue fine, ferror(stdout) = 0 (but no write to terminal): 
    int rtn; 
    if ((rtn = printf("printf after close(STDOUT_FILENO)\n")) < 0 || ferror(stdout)) 
    fprintf(stderr, "Error on printf after close(STDOUT_FILENO)\n"); 
    fprintf(stderr, "printf returned %d\n", rtn); 
    // Only on fflush is error detected: 
    if (fflush(stdout) || ferror(stdout)) 
    fprintf(stderr, "Error on fflush(stdout): %s\n", strerror(errno)); 
} 

Sans cette première printf, les RTN printf suivantes 34 comme si Une erreur s'est produite même si la connexion entre le tampon utilisateur stdout et le fd sous-jacent a été fermée. Ce n'est que sur un fflush manuel (stdout) que l'erreur est renvoyée. Mais avec ce premier printf allumé, le printf suivant signale des erreurs comme je m'attendais. Bien sûr, rien n'est écrit dans le terminal (par printf) après que STDOUT_FILENO fd ait été fermé dans les deux cas.

Je sais qu'il est idiot de close(STDOUT_FILENO) en premier lieu ici; C'est une expérience dans laquelle je suis tombé et qui pense que quelqu'un de plus compétent dans ces domaines peut voir quelque chose d'instructif dans nous.

Je suis sur Linux avec gcc.

+0

'stdout' est dans un mauvais état lorsque vous avez fermé le descripteur sous-jacent' STDOUT_FILENO' les choses fonctionnent correctement avec 'fclose (stdout)'. –

Répondre

4

Si vous strace les programmes les deux, il semble que stdio œuvres de sorte que lors premier écriture, il vérifie le descripteur avec fstat pour savoir quel type de fichier est connecté au stdout - si elle est un terminal, puis stdout doit être ligne-buffered, si c'est quelque chose d'autre, alors stdout sera tamponné en bloc. Si vous appelez close(1); avant le premier printf, maintenant le fstat initial renverra EBADF et comme 1 n'est pas un descripteur de fichier qui pointe vers un périphérique de caractères, stdout est rendu en bloc-buffer.

Sur mon ordinateur la taille de la mémoire tampon est de 8192 octets - que de nombreux octets peuvent être mises en mémoire tampon à écrire à stdoutavant la première défaillance se produirait.


Si vous supprimez la première printf, la fstat(1, ...)réussit et glibc détecte que stdout est connectée à une borne; stdout est mis en ligne-buffered, et ainsi parce que printf after close(STDOUT_FILENO)\n se termine avec newline, le tampon sera immédiatement vidé - ce qui entraînera une erreur immédiate.

+0

Ce qui est étrange, c'est que l'impression de 1024 caractères conduit à un changement d'état ... Une boucle sur le printf peut facilement l'afficher. –

+1

Oui, cette configuration interne de la mémoire tampon via l'appel 'fstat' que vous avez décrite, semble cohérente avec ce que' man setbuf' dit: "La fonction setvbuf() peut être utilisée seulement après l'ouverture d'un flux et avant toute autre opération effectué sur elle. " Si je remplace ce premier 'printf' par' setlinebuf (stdout); 'il se comporte de la même manière. –

+0

Donc une mise en garde analogue semble s'appliquer à 'printf' comme celui qui apparaît dans les notes de' man 2 write': "Un retour réussi de write() ne garantit pas que les données ont été validées sur le disque." en remplaçant 'write()' par 'printf()' et 'disk' par 'buffer du fichier noyau'. Ici 'fflush' et' fsync' seraient des analogues. Je ne connais pas d'analogue de 'close (fd)' qui couperait la connexion entre le tampon du noyau et le disque. –