Provavelmente seu professor não explicou isso direito pra você! (parte 2)

Neste outro artigo, aqui, eu te expliquei o funcionamento do stream stdout e do uso de fflush(). Mas, deixei alguns detalhes de fora, especialmente quanto ao stream stdin.

Em um grupo do Facebook tenho visto uma galera usando fflush(stdin) em resoluções de exercícios, usando linguagem C. Essa construção é inútil porque, por padrão, fflush() funciona apenas para streams que podem ser escritas. Este é o caso de stdout.

Outro detalhe é sobre o uso da função scanf(). Ela tem uns problemas que podem ser desesperadores para o novato.

Também têm um valor de retorno. A função devolve um valor do tipo int que é o número de itens que puderam ser convertidos na “varredura” (scan) ou -1, em caso de erro catastrófico. Se você fizer:

int a, b, n;

n = scanf("%d %d", &a, &b);

E entrar com duas strings não numéricas, vai obter n diferente de 2. Se entrar com “xpto xpto”, obterá n=0. Se entrar com “1 xpto”, obterá n=1… A função também interpreta ‘\n’ como “espaço” a ser ignorado. Se você entrar com apenas “1”, a função continuará esperando pelo segundo argumento a ser convertido!

O termo “varredura” é importante! A função procura pelos itens a serem convertidos, de acordo com o formato especificado, varrendo o stream stdin, até conseguir convertê-los ou falhar, ignorando os “espaços”. No exemplo acima, se entrássemos com:

"1          1" ou "       1 1"

Teríamos a e b convertidos corretamente.

No caso de itens que não podem ser convertidos, aparece um problema: Eles permanecem no buffer de stdin! Para demonstrar isso, eis um exemplo:

#include <stdio.h>

void main(void)
{
  int a, b, n;

  for (;;) {
    printf("Entre com dois números: "); fflush(stdout);
    n = scanf("%d %d", &a, &b);
    printf("%d valores convertidos: %d e %d\n", n, a, b);
  }
}

Compile e execute esse programinha e entre com “abc abc”… Observe os printf‘s sendo executados um monte de vezes! Isso acontece porque scanf() deixará no buffer de stdin os caracteres não convertidos da string lida anteriormente (como expliquei ai em cima), bem como qualquer caracter “não pulado”. Assim, scanf() tentará reconvertê-los, falhando diversas vezes até limpar todo o buffer de stdin.

Infelizmente não há jeito portável de fazer um flushing no stream stdin… Mas, usando uma extensão da glibc podemos corrigir esse problema facilmente:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  char *line;
  size_t line_size;
  int n;

  unsigned char b;
  char str[21];

  do {
    printf("Entre com um valor de 8 bits e uma string separados por espaço:\n");

    // força alocação da linha em getline().
    line_size = 0;
    line = NULL;

    // Lê uma linha de stdin.
    // getline() é uma extensão da glibc!
    if (getline(&line, &line_size, stdin) == -1)
    {
      fputs("ERRO alocando espaço para conter a linha!\n", stderr);
      exit(EXIT_FAILURE);
    }

    // Para termos um valor definido, em caso de falha.
    str[0] = b = 0;

    // Usa sscanf() porque quando scanf falha, deixa o stream stdin com lixo!
    n = sscanf(line, "%hhu %20s", &b, str);

    // livra-se da linha alocada.
    free(line);

    printf("%d itens convertidos: %hhu, \"%s\"\n", n, b, str);
  } while (1);
}

Aqui, sscanf uão usa um stream para obter os valores convertidos, ela usa uma string! A função getline() lerá uma linha da entrada (stdin) — ou seja, até char um ‘\n’, que será colocado no final do buffer alocado, precedendo o caracter NUL — e retornará com buffer apontado por line dinamicamente alocado (ou retorna -1 em caso de falha). Note que depois usar o sscanf fiz uma chamada a free(), livrando-se do buffer alocado por getline.

Se você digitar ENTER e obter uma linha vazia, ela terá 1 único caracter (‘\n’) o o NUL. Assim, sscanf falhará, mas não há nenhum buffer mantido por um stream para bagunçar o coreto.

Já que getline() é uma extensão, você pode optar por usar fgets(), mas perderá o recurso de alocação dinâmica automática, tendo de codificar isso por si mesmo… Ahhh… não use gets() para ler o stream stdin. Essa função é obsoleta desde a especificação C99 e não deve ser mais usada.

Anúncios