Ir para o conteúdo
Localhost

Buffering

Mensagens Recomendadas

Localhost

Alguém me pode explicar por que razão é que quando compilo e executo o código que vou mostrar a seguir o programa espera os 3 segundos (das 3 iterações do ciclo) e só depois executa a função putchar?

#include <stdio.h>

int main (void)
{
    int k = 0;

    for (k = 0; k < 3; k++)
    {
 putchar ('.');
 sleep (1);
    }

    return 0;
}

Em vez de ir imprimindo e esperando 1 segundo, o programa espera 3 segundos e só depois imprime 3 pontos.


here since 2009

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
Baderous

http://stackoverflow.com/questions/4201083/c-sleep-delays-output

Se experimentares com o seguinte código, vais ver a diferença.

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

int main (void)
{
    int k = 0;
    for (k = 0; k < 3; k++)
    {
       putchar ('.');
       sleep (1);
       putchar('\n');
    }

    return 0;
}

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
Baderous

O stdout é uma stream que tem um buffer associado (assim como o stdin e o stderr) para o qual se escrevem os caracteres que vão ser apresentados ao utilizador. O buffer pode ter 3 modos de acção diferentes para o caso do output:

1) No buffering, isto é, mal os dados estejam disponíveis na stream são apresentados ao utilizador;

2) Line-buffering, isto é, os dados são escritos para um buffer e só são apresentados quando é escrito um newline ou quando é atingido o tamanho máximo do buffer, sendo o buffer limpo;

3) Full-buffered, isto é, os dados são escritos para um buffer e são apresentados quando o buffer enche, sendo depois limpo.

Este comportamento é dependente da implementação, mas em Unix, o stdout é line-buffered, daí que siga o comportamento descrito. Ou seja, no teu caso, os caracteres estão a ser colocados no buffer e só são despejados quando o programa termina (quando termina, todos os buffers são flushed).

Existem várias formas de despejar o conteúdo no caso dos buffers line-buffered:

1) Fazer print para o stderr que é unbuffered;

2) Fazer print do caracter de mudança de linha;

3) Obrigar a despejar o conteúdo, fazendo um flush: fflush(stdout);

4) Tornar o stdout unbuffered: setvbuf (stdout, NULL, _IONBF, BUFSIZ);

Why does printf not flush after the call unless a newline is in the format string? (in C)

setvbuf

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
Localhost

Excelente explicação. Obrigado.

Já agora, qual é o tipo de buffering que o stdin apresenta? E quando o mesmo é flushed o seu conteúdo vai para onde?

EDIT:

Vou mudar o título do tópico.


here since 2009

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
brunoais

Já agora, qual é o tipo de buffering que o stdin apresenta? E quando o mesmo é flushed o seu conteúdo vai para onde?

A minha experiencia diz que não é suposto usar o fflush() para o stdin porque n faz sentido segundo o conceito da função.

O stdin é full-buffered. Guarda tudo até se ir buscar ou até ficar cheio. Mas não sei o que acontece qd fica cheio pq nunca experimentei.


"[Os jovens da actual geração]não lêem porque não envolve um telecomando que dê para mirar e atirar, não falam porque a trapalhice é rainha e o calão é rei" autor: thoga31

Life is a genetically transmitted disease, induced by sex, with death rate of 100%.

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
brunoais

Full-buffered?

Sim (troquei-me, sorry)


"[Os jovens da actual geração]não lêem porque não envolve um telecomando que dê para mirar e atirar, não falam porque a trapalhice é rainha e o calão é rei" autor: thoga31

Life is a genetically transmitted disease, induced by sex, with death rate of 100%.

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
Baderous

O stdin não é full-buffered, é line-buffered, porque está associado a um terminal, por omissão (em sistemas Unix). Por exemplo, quando estamos a fazer um scanf (que está a colocar os dados no buffer do stdin), quando carregamos no ENTER ('\n'), o buffer é despejado. Só é full-buffered se tiver sido redireccionado para apontar, por exemplo, para um ficheiro de onde vão ser lidos os caracteres.

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
Baderous

Quando lês uma string com scanf, o '\n' fica no buffer, é por isso que surgem muitas vezes aqui questões a dizer que o programa avança uma instrução de leitura inesperadamente. O buffer que em que estás a pensar quando dizes que "fica cheio" não é o buffer associado às streams, mas o "buffer" para onde vão ser guardados os caracteres aquando da leitura da string, ou seja, a própria string alocada na stack. O tamanho do buffer associado às streams é dado pela macro BUFSIZ, a qual deve ter, no mínimo, um tamanho de 256. No GCC, é usado o valor 1024.

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
Localhost

Então por que é que essa tal instrução de leitura é "avançada"?

Vê se este código e os seus respectivos comentários estão correctos:

scanf ("%s", buf1); // lê para a string buf1 e deixa no buffer associado à stream o \n
scanf ("%s", buf2); // como encontra o \n no buffer associado à stream, esse mesmo buffer é flushed e buf2 vai receber o que se encontra na stream
// neste caso é, então, lido o despejo da stream


here since 2009

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
Baderous

Deixa-me clarificar aqui uma coisa que acho que é isso que está a fazer confusão. Quando falo no flush do stdout refiro-me a despejar todo o seu conteúdo para o terminal. No caso do stdin, é feito o flush do conteúdo para as variáveis em questão, ele não é completamente despejado, daí os problemas do "tens de limpar o buffer de entrada porque tem lá lixo". Ele é despejado quando recebe um '\n' por parte do utilizador.

Para esse caso, a tua interpretação do 1º scanf está correcta. No 2º caso, o '\n' já estava lá, não foi introduzido na nova leitura, logo o flush só acontece quando lá colocas um novo '\n', desde que este seja precedido por caracteres non-whitespace:

Whitespace character: the function will read and ignore any whitespace characters (this includes blank spaces and the newline and tab characters) which are encountered before the next non-whitespace character. This includes any quantity of whitespace characters, or none.

Se testares isso, se na 2ª leitura só fizeres ENTER, ele não sai da função enquanto não colocares lá outro caracter e deres o ENTER.

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
Localhost

O que eu queria dizer era que como na 2ª leitura vai encontrar o \n na stream vai logo passar à próxima instrução. Certo?

O pequeno "hack" do espaço em branco é justamente não permitir que passe logo à próxima instrução sem ler apenas o \n. Certo?


here since 2009

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
Baderous

O que eu queria dizer era que como na 2ª leitura vai encontrar o \n na stream vai logo passar à próxima instrução. Certo?

O pequeno "hack" do espaço em branco é justamente não permitir que passe logo à próxima instrução sem ler apenas o \n. Certo?

Não estou certo do que vou dizer, mas penso que o passar à próxima instrução devido à existência de um '\n' no buffer acontece em todos os casos de leitura (ints, chars, etc), excepto para as strings. O hack do espaço em branco permite ignorar o '\n' nesses tais casos. Mas isto já não tenho a certeza, pelos testes que fiz parece-me ser esse o comportamento.

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
daj

Considerando o texto http://pubs.opengroup.org/onlinepubs/9699919799/functions/scanf.html

Input white-space characters (as specified by isspace ) shall be skipped, unless the conversion specification includes a [ , c , C , or n conversion specifier.
s: Matches a sequence of bytes that are not white-space characters.

e o código:

scanf("%s", buf1);
scanf("%c", &c1);
scanf("%s", buf2);
scanf("%c", &c2);
scanf("%s", buf3);

A execução será:

scanf("%s", buf1): O buffer não tem dados por isso é pedida uma linha ao utilizador. A função fica bloqueada até receber um enter e o conteúdo dessa linha é colocado no buffer. Suponha-se que o utilizador insere "aaa<space><space><space>bbb<enter>". É lido "aaa" para o buf1 porque a seguir vem um "white-space character" (<space>, <tab>, <newline>, <vertical-tab> ou <form-feed> - neste caso é um espaço). Buffer fica com "<space><space><space>bbb<enter>".

scanf("%c", &c1): Existem dados no buffer por isso não é pedido nada ao utilizador. É lido "<space>" para c1. Buffer fica com "<space><space>bbb<enter>".

scanf("%s", buf2): Ainda existem dados. Sendo pedida uma string, são despachados os white-space characters do início (ver primeira citação). Buffer fica com "bbb<enter>". É lido "bbb" para buf2 porque a seguir vem um white-space character (neste caso, um enter). Buffer fica com "<enter>".

scanf("%c", &c2): Ainda existem dados. É lido "<enter>" para c2. Buffer fica vazio.

scanf("%s", buf3): Buffer está vazio, por isso é pedida uma nova linha ao utilizador. Mesmo comportamento que o primeiro scanf.

Estou correcto na minha análise e, se sim, ajudou alguém?

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
Localhost

@daj: já pensaste que ele pode ler tudo para o stdin e só depois (quando é despejado) fazer um género de uma filtragem dos dados? Ou seja, lê tudo para o buffer e depois quando encontra o \n é despejado e "envia" para a variável tudo menos espaços em branco, etc. e deixa também no buffer o \n.

Podemos tirar daqui muitas teorias mas não estou a encontrar nenhuma conclusiva.


here since 2009

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
Localhost

Acho que consegui um exemplo para perceber isto.

#include <stdio.h>

int main (void)
{
char c1, c2, s1[1024];

printf ("s1: ");
scanf ("%s", s1); // guarda no stdin com o \n

printf ("c1: ");
scanf ("%c", &c1); // encontra o \n e despeja o buffer; apenas aqui s1 recebe a string introduzida; não espera por input aqui;

printf ("c2: ");
scanf ("%c", &c2); // buffer está vazio; lê normalmente

system ("clear");
printf ("s1: %s\nc1: %c\nc2: %c\n", s1, c1, c2); // c1 contém um \n

return 0;
}

É isto...?


here since 2009

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites
daj

Localhost,

experimenta colocar um printf de s1 antes do printf ("c1: ") e verás que s1 já tem os dados. Nem seria fácil o scanf memorizar apontadores passados em invocações anteriores para no scanf ("%c", &c1) saber escrever em s1. Se o utilizador escreveu "aaa<enter>", então no fim da execução do primeiro scanf, s1 tem "aaa" e o buffer do stdin ainda tem lá o "<enter>". No segundo scanf não espera por input porque o "<enter>" restante do input que se pediu no primeiro scanf é suficiente para satisfazer o %c.

Partilhar esta mensagem


Ligação para a mensagem
Partilhar noutros sites

Crie uma conta ou ligue-se para comentar

Só membros podem comentar

Criar nova conta

Registe para ter uma conta na nossa comunidade. É fácil!

Registar nova conta

Entra

Já tem conta? Inicie sessão aqui.

Entrar Agora

×

Aviso Sobre Cookies

Ao usar este site você aceita os nossos Termos de Uso e Política de Privacidade. Este site usa cookies para disponibilizar funcionalidades personalizadas. Para mais informações visite esta página.