Ringraziamo Jonathan Leffler per averci indicato la giusta direzione.
Sebbene il tuo programma non produca lo stesso comportamento imprevisto per me su CentOS 7 / GCC 4.8.5 / GLIBC 2.17, è plausibile che tu osservi un comportamento diverso. Il comportamento del tuo programma è infatti indefinito secondo POSIX (su cui fai affidamento per fork
). Ecco alcuni estratti dalla sezione pertinente (corsivo aggiunto):
È possibile accedere alla descrizione di un file aperto tramite un descrittore di file, che viene creato utilizzando funzioni come
open()
opipe()
, o attraverso uno stream, che viene creato usando funzioni comefopen()
opopen()
.O un descrittore di file o uno stream è chiamato "handle" sulla descrizione di openfile a cui fa riferimento; una descrizione di un file aperto può avere diversi handle.[...]
Il risultato delle chiamate di funzione che coinvolgono un qualsiasi handle (l'"activehandle") è definito altrove in questo volume di POSIX.1-2017, ma se vengono utilizzati due o più handle e uno di essi è uno stream, l'applicazione deve garantire che il loro le azioni sono coordinate come descritto di seguito. Se ciò non viene fatto, il risultato non è definito .
[...]
Affinché un handle diventi l'handle attivo, l'applicazione deve garantire che le azioni seguenti vengano eseguite tra l'ultimo utilizzo dell'handle (l'attuale handle attivo) e il primo utilizzo del secondo handle (il futuro handle attivo). La seconda maniglia diventa quindi la maniglia attiva. [...]
Non è necessario che gli handle si trovino nello stesso processo affinché queste regole vengano applicate.
Nota che dopo un
fork()
, esistono due handle dove uno esisteva prima. L'applicazione deve garantire che, se è possibile accedere a entrambi gli handle, si trovino entrambi in uno stato in cui l'altro potrebbe diventare per primo l'handle attivo. [Laddove soggetta alla precedente qualificazione, la] domanda deve preparare unfork()
esattamente come se si trattasse di un cambio di maniglia attiva. (Se l'unica azione eseguita da uno dei processi è una delle funzioni exec o_exit()
(nonexit()
), l'handle non è mai accessibile in quel processo. )Per il primo handle, si applica la prima condizione applicabile di seguito.[Un elenco incredibilmente lungo di alternative che non si applicano alla situazione dell'OP...]
- Se il flusso è aperto con una modalità che consente la lettura e la descrizione sottostante del file aperto fa riferimento a un dispositivo in grado di eseguire la ricerca, l'applicazione deve eseguire un
fflush()
, o lo stream verrà chiuso.Per la seconda maniglia:
- Se un precedente handle attivo è stato utilizzato da una funzione che ha modificato esplicitamente l'offset del file, ad eccezione di quanto richiesto sopra per il primo handle, l'applicazione deve eseguire un
lseek()
ofseek()
(a seconda del tipo di maniglia) in una posizione appropriata.
Pertanto, affinché il programma dell'OP possa accedere allo stesso flusso sia nel genitore che nel figlio, POSIX richiede che il genitore fflush()
stdin
prima del fork e che il figlio fseek()
dopo l'avvio. Quindi, dopo aver atteso che il figlio termini, il genitore deve fseek()
il flusso. Dato che sappiamo che l'esecutivo del figlio fallirà, tuttavia, il requisito per tutto il lavaggio e la ricerca può essere evitato facendo in modo che il bambino usi _exit()
(che non accede allo stream) invece di exit()
.
Il rispetto delle disposizioni di POSIX produce quanto segue:
Quando queste regole vengono seguite, indipendentemente dalla sequenza di handle utilizzati, le implementazioni devono garantire che un'applicazione, anche composta da più processi, fornisca risultati corretti:nessun dato deve essere perso o duplicato durante la scrittura e tutti i dati devono essere scritti in ordine, ad eccezione di richiesto da seek.
Vale la pena notare, tuttavia, che
È definito dall'implementazione se, e in quali condizioni, tutti gli input vengono visti esattamente una volta.
Mi rendo conto che potrebbe essere in qualche modo insoddisfacente sentire semplicemente che le tue aspettative per il comportamento del programma non sono giustificate dagli standard pertinenti, ma questo è davvero tutto ciò che c'è. I processi padre e figlio hanno alcuni dati condivisi rilevanti sotto forma di una descrizione di file aperto comune (a cui sono associati handle separati) e sembra probabile che sia il veicolo per il comportamento imprevisto (e non definito), ma non c'è alcuna base per prevedere il comportamento specifico che vedi, né il diverso comportamento che vedo per lo stesso programma.