Por que printf não libera após a chamada, a menos que uma nova linha esteja na string de formato?

539

Por que printfnão descarrega após a chamada, a menos que uma nova linha esteja na cadeia de formato? Esse comportamento é POSIX? Como posso printflavar imediatamente todas as vezes?

Crazy Chenz
fonte
2
você investigou se isso acontece com algum arquivo ou apenas com terminais? que soaria a ser uma característica de terminal inteligente não saída de linha incompleta a partir de um programa em segundo plano, embora eu espere que ele não se aplicaria a do programa de primeiro plano.
PypeBros #
7
Em Cygwin bash, estou vendo esse mesmo mau comportamento, mesmo que uma nova linha esteja na string de formato. Esse problema é novo no Windows 7; o mesmo código fonte funcionou bem no Windows XP. MS cmd.exe é liberado conforme o esperado. A correção setvbuf(stdout, (char*)NULL, _IONBF, 0)soluciona o problema, mas certamente não deveria ter sido necessária. Estou usando o MSVC ++ 2008 Express. ~~~
Steve Pitchers
9
Para esclarecer o título da pergunta: printf(..) não faz nenhuma descarga , é o buffer stdoutque pode liberar ao ver uma nova linha (se estiver com buffer de linha). Ele reagiria da mesma maneira putchar('\n');, portanto printf(..)não é especial a esse respeito. Isso contrasta com cout << endl;a documentação mencionada com destaque na descarga. A documentação do printf não menciona a descarga.
Evgeni Sergeev
1
escrever (/ liberar) é potencialmente uma operação cara, provavelmente está armazenada em buffer por razões de desempenho.
precisa saber é o seguinte
@ EvgeniSergeev: Existe um consenso de que a pergunta diagnosticou incorretamente o problema e que a descarga ocorre quando há uma nova linha na saída ? (colocar um na string de formato é uma maneira, mas não a única, de obter uma na saída).
Ben Voigt

Respostas:

702

O stdoutfluxo é buffer de linha por padrão, portanto, somente exibirá o conteúdo do buffer depois que ele atingir uma nova linha (ou quando for solicitado). Você tem algumas opções para imprimir imediatamente:

Imprimir para stderrusar em vez disso fprintf( stderré sem buffer por padrão ):

fprintf(stderr, "I will be printed immediately");

Libere o stdout sempre que precisar, usando fflush:

printf("Buffered, will be flushed");
fflush(stdout); // Will now print everything in the stdout buffer

Edit : No comentário de Andy Ross abaixo, você também pode desativar o buffer no stdout usando setbuf:

setbuf(stdout, NULL);

ou sua versão segura, setvbufconforme explicado aqui

setvbuf(stdout, NULL, _IONBF, 0); 
Rudd Zwolinski
fonte
266
Ou, para desativar o buffer completamente:setbuf(stdout, NULL);
Andy Ross
80
Além disso, só queria mencionar que, aparentemente, no UNIX, uma nova linha normalmente apenas liberará o buffer se stdout for um terminal. Se a saída estiver sendo redirecionada para um arquivo, uma nova linha não será liberada.
hora
5
Sinto que devo acrescentar: acabei de testar essa teoria e estou descobrindo que o uso setlinebuf()em um fluxo que não é direcionado a um terminal está liberado no final de cada linha.
Doddy 6/09/11
8
"Como aberto inicialmente, o fluxo de erro padrão não é totalmente armazenado em buffer; os fluxos de entrada e saída padrão são totalmente armazenados em buffer se e somente se for possível determinar que o fluxo não se refere a um dispositivo interativo" - consulte esta pergunta: stackoverflow.com / questions / 5229096 /…
Seppo Enarvi
3
@RuddZwolinski Se essa for uma boa resposta canônica de "por que não está imprimindo", parece importante mencionar a distinção entre terminal / arquivo, conforme "O printf sempre limpa o buffer ao encontrar uma nova linha?" diretamente nesta resposta altamente votada, contra as pessoas que precisam ler os comentários ... #
HostileFork diz que não confia em SE
128

Não, não é um comportamento POSIX, é um comportamento ISO (bem, é um comportamento POSIX, mas apenas na medida em que estejam em conformidade com a ISO).

A saída padrão é buffer de linha se puder ser detectada para se referir a um dispositivo interativo, caso contrário, é totalmente buffer. Portanto, há situações em printfque a descarga não ocorre , mesmo que receba uma nova linha para enviar, como:

myprog >myfile.txt

Isso faz sentido para a eficiência, pois, se você está interagindo com um usuário, ele provavelmente deseja ver todas as linhas. Se você estiver enviando a saída para um arquivo, é mais provável que não haja um usuário na outra extremidade (embora não seja impossível, eles podem estar seguindo o arquivo). Agora você pode argumentar que o usuário deseja ver todos os personagens, mas há dois problemas com isso.

A primeira é que não é muito eficiente. A segunda é que o mandato original da ANSI C era principalmente codificar o comportamento existente , em vez de inventar um novo comportamento, e essas decisões de design foram tomadas muito antes do início da ANSI. Atualmente, até a ISO caminha com muito cuidado ao alterar as regras existentes nos padrões.

Quanto a como lidar com isso, se você fflush (stdout)após cada chamada de saída que deseja ver imediatamente, isso resolverá o problema.

Como alternativa, você pode usar setvbufantes de operar stdoutpara defini-lo como sem buffer e não precisará se preocupar em adicionar todas essas fflushlinhas ao seu código:

setvbuf (stdout, NULL, _IONBF, BUFSIZ);

Lembre-se de que isso pode afetar bastante o desempenho se você estiver enviando a saída para um arquivo. Lembre-se também de que o suporte a isso é definido pela implementação, não garantido pelo padrão.

A seção ISO C99 7.19.3/3é o bit relevante:

Quando um fluxo não é armazenado em buffer , os caracteres devem aparecer da origem ou do destino o mais rápido possível. Caso contrário, os caracteres podem ser acumulados e transmitidos para ou do ambiente host como um bloco.

Quando um fluxo é totalmente armazenado em buffer , os caracteres devem ser transmitidos para ou a partir do ambiente host como um bloco quando um buffer é preenchido.

Quando um fluxo é buffer de linha , os caracteres devem ser transmitidos para ou do ambiente host como um bloco quando um caractere de nova linha é encontrado.

Além disso, os caracteres devem ser transmitidos como um bloco para o ambiente host quando um buffer é preenchido, quando a entrada é solicitada em um fluxo sem buffer ou quando a entrada é solicitada em um fluxo com buffer de linha que requer a transmissão de caracteres do ambiente host. .

O suporte para essas características é definido pela implementação e pode ser afetado pelas funções setbufe setvbuf.

paxdiablo
fonte
8
Acabei de me deparar com um cenário em que mesmo existe um '\ n', printf () não fica nivelado. Foi superado adicionando um fflush (stdout), como você mencionou aqui. Mas estou me perguntando a razão pela qual '\ n' falhou ao liberar o buffer em printf ().
Qiang Xu
11
@QiangXu, a saída padrão é buffer de linha apenas no caso em que pode ser definitivamente determinado a se referir a um dispositivo interativo. Portanto, por exemplo, se você redirecionar a saída com myprog >/tmp/tmpfile, isso é totalmente armazenado em buffer, em vez de com buffer de linha. A partir da memória, a determinação sobre se sua saída padrão é interativa é deixada para a implementação.
precisa
3
além disso, no Windows, chamando setvbuf (...., _IOLBF) não funcionará, pois _IOLBF é o mesmo que _IOFBF: msdn.microsoft.com/en-us/library/86cebhfs.aspx
Piotr Lopusiewicz
28

Provavelmente é assim por causa da eficiência e porque se você tiver vários programas gravando em um único TTY, dessa forma, não haverá caracteres entrelaçados em uma linha. Portanto, se os programas A e B estiverem sendo exibidos, você normalmente obterá:

program A output
program B output
program B output
program A output
program B output

Isso fede, mas é melhor do que

proprogrgraam m AB  ououtputputt
prproogrgram amB A  ououtputtput
program B output

Observe que nem é garantido que você libere uma nova linha; portanto, você deve liberar explicitamente se a liberação for importante para você.

Hospitalidade do Sul
fonte
26

Lavar imediatamente a chamada fflush(stdout)ou fflush(NULL)( NULLsignifica lavar tudo).

Aaron
fonte
31
Lembre- fflush(NULL);se geralmente é uma péssima idéia. Isso prejudicará o desempenho se você tiver muitos arquivos abertos, especialmente em um ambiente com vários threads, onde você lutará com tudo por bloqueios.
R .. GitHub Pare de ajudar o gelo
14

Nota: As bibliotecas de tempo de execução da Microsoft não suportam buffer de linha, portanto printf("will print immediately to terminal"):

https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setvbuf

Renato
fonte
3
Pior do que printfir imediatamente para o terminal no caso "normal" é o fato de que printfe fprintfobter mais grosseiramente tamponada, mesmo nos casos em que a sua saída é colocar para uso imediato. A menos que o MS tenha consertado as coisas, isso torna impossível para um programa capturar stderr e stdout de outro e identificar em qual sequência as coisas foram enviadas para cada um.
Supercat
não, não imprime isso imediatamente no terminal, a menos que nenhum buffer tenha sido definido. Por padrão, o buffer completo é usado
phuclv 26/04
12

stdout é armazenado em buffer, portanto, somente será produzido após a impressão de uma nova linha.

Para obter saída imediata, ou:

  1. Imprimir para stderr.
  2. Faça stdout sem buffer.
Douglas Leeder
fonte
10
Or fflush(stdout).
RastaJedi 22/02
2
"assim, somente será produzido após a impressão de uma nova linha." Não apenas isso, mas pelo menos outros 4 casos. buffer cheio, escreva para stderr(esta resposta menciona mais tarde), fflush(stdout), fflush(NULL).
chux - Restabelece Monica
11

por padrão, stdout é buffer de linha, stderr não é buffer e arquivo é completamente armazenado em buffer.

quem
fonte
10

Você pode imprimir para stderr, que não é armazenado em buffer. Ou você pode liberar o stdout quando quiser. Ou você pode definir stdout como sem buffer.

Rasmus Kaj
fonte
10

Use setbuf(stdout, NULL);para desativar o buffer.

dnahc araknayirp
fonte
2

Geralmente existem 2 níveis de buffering

1. Cache do buffer do kernel (torna a leitura / gravação mais rápida)

2. Buffer na biblioteca de E / S (reduz o número de chamadas do sistema)

Vamos dar um exemplo de fprintf and write().

Quando você liga fprintf(), ele não é direcionado diretamente para o arquivo. Primeiro ele vai para o buffer stdio na memória do programa. A partir daí, ele é gravado no cache do buffer do kernel usando a chamada de sistema de gravação. Portanto, uma maneira de pular o buffer de E / S é diretamente usando write (). Outras maneiras são usando setbuff(stream,NULL). Isso define o modo de buffer para nenhum buffer e os dados são gravados diretamente no buffer do kernel. Para forçar os dados a serem deslocados para o buffer do kernel, podemos usar "\ n", que no caso do modo de buffer padrão do 'buffer de linha', liberará o buffer de E / S. Ou podemos usar fflush(FILE *stream).

Agora estamos no buffer do kernel. O kernel (/ OS) deseja minimizar o tempo de acesso ao disco e, portanto, lê / grava apenas blocos de disco. Portanto, quando a read()é emitida, que é uma chamada do sistema e pode ser chamada diretamente ou através fscanf()dela, o kernel lê o bloco de disco do disco e o armazena em um buffer. Depois que os dados são copiados daqui para o espaço do usuário.

Da mesma forma, os fprintf()dados recebidos do buffer de E / S são gravados no disco pelo kernel. Isso torna o read () write () mais rápido.

Agora, para forçar o kernel a iniciar um write(), após o qual a transferência de dados é controlada pelos controladores de hardware, também existem algumas maneiras. Podemos usar O_SYNCou sinalizadores semelhantes durante chamadas de gravação. Ou podemos usar outras funções, como fsync(),fdatasync(),sync()para fazer com que o kernel inicie gravações assim que os dados estiverem disponíveis no buffer do kernel.

o_O
fonte