Merci à Jonathan Leffler de nous avoir orientés dans la bonne direction.
Bien que votre programme ne produise pas le même comportement inattendu pour moi sur CentOS 7 / GCC 4.8.5 / GLIBC 2.17, il est plausible que vous observiez un comportement différent. Le comportement de votre programme est en fait indéfini selon POSIX (sur lequel vous vous appuyez pour fork
). Voici quelques extraits de la section pertinente (nous soulignons) :
Une description de fichier ouverte est accessible via un descripteur de fichier, qui est créé à l'aide de fonctions telles que
open()
oupipe()
, ou via un flux créé à l'aide de fonctions telles quefopen()
oupopen()
Soit un descripteur de fichier, soit un flux est appelé un "descripteur" sur la description de fichier ouvert à laquelle il se réfère ; une description de fichier ouverte peut avoir plusieurs descripteurs.[...]
Le résultat des appels de fonction impliquant un handle (le "handle actif") est défini ailleurs dans ce volume de POSIX.1-2017, mais si deux handles ou plus sont utilisés, et que l'un d'entre eux est un flux, l'application doit s'assurer que leur les actions sont coordonnées comme décrit ci-dessous. Si cela n'est pas fait, le résultat est indéfini .
[...]
Pour qu'un handle devienne le handle actif, l'application doit s'assurer que les actions ci-dessous sont effectuées entre la dernière utilisation du handle (le handle actif actuel) et la première utilisation du deuxième handle (le futur handle actif). La deuxième poignée devient alors la poignée active. [...]
Les descripteurs n'ont pas besoin d'être dans le même processus pour que ces règles s'appliquent.
Notez qu'après un
fork()
, deux descripteurs existent là où il n'en existait qu'un auparavant. L'application doit s'assurer que, si les deux descripteurs sont accessibles, ils sont tous les deux dans un état où l'autre pourrait devenir le descripteur actif en premier. [Lorsque sous réserve de la qualification précédente, la] candidature doit préparer unfork()
exactement comme s'il s'agissait d'un changement de poignée active. (Si la seule action effectuée par l'un des processus est l'une des fonctions exec ou_exit()
(pasexit()
), le handle n'est jamais accessible dans ce processus. )Pour la première poignée, la première condition applicable ci-dessous s'applique.[Une liste impressionnante d'alternatives qui ne s'appliquent pas à la situation de l'OP...]
- Si le flux est ouvert avec un mode qui permet la lecture et que la description du fichier ouvert sous-jacent fait référence à un périphérique capable de rechercher, l'application doit soit effectuer un
fflush()
, ou le flux sera fermé.Pour la seconde poignée :
- Si un descripteur actif précédent a été utilisé par une fonction qui a explicitement changé le décalage du fichier, sauf comme requis ci-dessus pour le premier descripteur, l'application doit effectuer un
lseek()
oufseek()
(selon le type de poignée) à un emplacement approprié.
Ainsi, pour que le programme de l'OP accède au même flux dans le parent et l'enfant, POSIX exige que le parent fflush()
stdin
avant de forker, et que l'enfant fseek()
après le démarrage. Ensuite, après avoir attendu que l'enfant se termine, le parent doit fseek()
le flux. Étant donné que nous savons que l'exec de l'enfant échouera, cependant, l'exigence de tout le rinçage et de la recherche peut être évitée en demandant à l'enfant d'utiliser _exit()
(qui n'accède pas au flux) au lieu de exit()
.
Se conformer aux dispositions de POSIX donne ce qui suit :
Lorsque ces règles sont suivies, quelle que soit la séquence des descripteurs utilisés, les implémentations doivent garantir qu'une application, même composée de plusieurs processus, donne des résultats corrects :aucune donnée ne doit être perdue ou dupliquée lors de l'écriture, et toutes les données doivent être écrites dans l'ordre, sauf si demandé par cherche.
Il convient toutefois de noter que
Il est défini par l'implémentation si, et dans quelles conditions, toutes les entrées sont vues exactement une fois.
Je comprends qu'il peut être quelque peu insatisfaisant d'entendre simplement que vos attentes en matière de comportement du programme ne sont pas justifiées par les normes pertinentes, mais c'est vraiment tout ce qu'il y a. Les processus parent et enfant ont des données partagées pertinentes sous la forme d'une description de fichier ouverte commune (avec laquelle ils ont des poignées séparées associées), et cela semble être le véhicule pour le comportement inattendu (et indéfini), mais il n'y a aucune base pour prédire le comportement spécifique que vous voyez, ni le comportement différent que je vois pour le même programme.