O contexto importante aqui é que stdout
é necessário para ser line buffered pelo padrão como configuração padrão.
Isso faz com que \n
libere a saída.
Como o segundo exemplo não contém a nova linha, a saída não é liberada e, como fork()
copia todo o processo, ele também copia o estado do buffer stdout
.
Agora, essas chamadas fork()
em seu exemplo criam 8 processos no total - todos eles com uma cópia do estado do buffer stdout
.
Por definição, todos esses processos chamam exit()
ao retornar de main()
e exit()
chamadas fflush()
seguido por fclose()
em todos os fluxos stdio ativos. Isso inclui stdout
e, como resultado, você vê o mesmo conteúdo oito vezes.
É uma boa prática chamar fflush()
em todos os fluxos com saída pendente antes de chamar fork()
ou permitir que o filho bifurcado chame explicitamente _exit()
que apenas sai do processo sem liberar os fluxos stdio.
Observe que chamar exec()
não libera os buffers stdio, portanto, não há problema nenhum em armazenar os buffers se você (após chamar fork()
) chamar exec()
e (se isso falhar) chamar _exit()
.
BTW: Para entender que o buffer errado pode causar, aqui está um antigo bug no Linux que foi corrigido recentemente:
O padrão requer que stderr
seja unbuffered por padrão, mas o Linux ignorou isso e fez stderr
line bufferizar e (pior ainda) armazenar completamente buffer no caso de o stderr ser redirecionado através de um pipe. Então, programas escritos para UNIX produziram coisas sem a nova linha, muito tarde no Linux.
Veja o comentário abaixo, parece ser corrigido agora.
Isso é o que eu faço para contornar esse problema do Linux:
/*
* Linux comes with a broken libc that makes "stderr" buffered even
* though POSIX requires "stderr" to be never "fully buffered".
* As a result, we would get garbled output once our fork()d child
* calls exit(). We work around the Linux bug by calling fflush()
* before fork()ing.
*/
fflush(stderr);
Esse código não prejudica outras plataformas, pois chamar fflush()
em um fluxo que acabou de ser liberado é noop.