Na programação em C, a comunicação entre o programa e o ambiente externo é feita através de fluxos (streams). Os três fluxos padrão — stdin, stdout e stderr — são pré-definidos e abertos automaticamente quando um programa é iniciado. Eles permitem que o programa receba entrada do usuário (teclado), envie saída para a tela e reporte erros, respectivamente.

Entender como esses fluxos funcionam é essencial para criar programas robustos e interativos, além de possibilitar o uso de redirecionamento e pipelines no sistema operacional.

stdin, stdout, stderr

O fluxo stdin (do inglês standard input) é a entrada padrão, normalmente associada ao teclado. É usado por funções como scanf, getchar e fgets para ler dados. O fluxo stdout (standard output) é a saída padrão, normalmente a tela do terminal, usado por printf e puts para exibir resultados. Já o stderr (standard error) também é destinado à tela, mas é reservado para mensagens de erro, sendo usado por fprintf(stderr, ...).

Esses fluxos são implementados como ponteiros do tipo FILE* e podem ser usados em qualquer função que aceite um fluxo. Por exemplo, fgets(buffer, tamanho, stdin) lê do teclado, enquanto fprintf(stderr, "Erro") envia para o fluxo de erro. A separação entre stdout e stderr é importante porque permite que, ao redirecionar a saída de um programa para um arquivo, as mensagens de erro ainda apareçam no terminal.

#include <stdio.h>

int main() {
    int numero;
    printf("Digite um número: ");
    scanf("%d", &numero);
    printf("Você digitou: %d\n", numero);
    fprintf(stderr, "Este é um erro de exemplo\n");
    return 0;
}

Redirecionamento

O redirecionamento é uma funcionalidade do shell (como Bash ou CMD) que permite alterar a origem ou destino dos fluxos padrão. Por exemplo, ./programa < entrada.txt faz com que o stdin seja lido do arquivo entrada.txt em vez do teclado. Da mesma forma, ./programa > saida.txt envia o stdout para o arquivo saida.txt.

O stderr pode ser redirecionado separadamente usando 2>. Exemplo: ./programa 2> erros.log envia apenas as mensagens de erro para o arquivo. Também é possível redirecionar ambos para o mesmo arquivo com > saida.txt 2>&1. Esses recursos são poderosos para automação e logging.

$ ./meu_programa < dados.txt > resultado.txt 2> erros.txt

Buffering (fflush)

Os fluxos em C são geralmente bufferizados, ou seja, os dados são acumulados em um buffer antes de serem enviados ao dispositivo. Para stdout, o buffer é descarregado quando o buffer está cheio, quando o programa termina, ou quando o fluxo é fechado. Em alguns casos, pode ser necessário forçar a escrita imediata, especialmente em programas interativos ou quando se usa printf antes de uma leitura.

A função fflush é usada para descarregar o buffer de um fluxo de saída. Por exemplo, fflush(stdout) força a escrita imediata de tudo que está no buffer. Isso é útil em situações como mostrar um prompt antes de ler uma entrada, pois sem o fflush o prompt pode não aparecer até que o buffer encha ou o programa termine.

#include <stdio.h>

int main() {
    printf("Digite algo: ");
    fflush(stdout);  // Garante que o prompt apareça
    char buffer[100];
    fgets(buffer, sizeof(buffer), stdin);
    printf("Você digitou: %s", buffer);
    return 0;
}

Exemplos

Vamos ver um exemplo completo que combina os conceitos de stdin, stdout e stderr. O programa lê um número, verifica se é positivo e imprime uma mensagem na saída padrão ou no erro, conforme o caso.

#include <stdio.h>

int main() {
    int valor;
    printf("Entre com um número: ");
    fflush(stdout);
    scanf("%d", &valor);
    if (valor > 0) {
        printf("Número positivo: %d\n", valor);
    } else {
        fprintf(stderr, "Erro: número não positivo\n");
    }
    return 0;
}

Outro exemplo mostra como redirecionar a saída para um arquivo dentro do próprio código, usando freopen. Isso é útil quando se deseja que a saída vá para um arquivo sem depender do shell.

#include <stdio.h>

int main() {
    freopen("saida.txt", "w", stdout);
    printf("Esta mensagem vai para o arquivo.\n");
    fclose(stdout);
    return 0;
}

Boas Práticas

Ao trabalhar com fluxos padrão, é importante usar stderr para mensagens de erro, pois isso permite que elas sejam separadas da saída normal. Sempre que ler dados com scanf, verifique o retorno para evitar problemas com entradas inválidas. Use fflush somente quando necessário, pois chamadas excessivas podem prejudicar a performance. Prefira fgets a scanf para ler strings, pois é mais seguro.

Referências

Exercícios

  1. Exercício 1: Escreva um programa em C que leia um caractere do stdin e imprima no stdout se é uma letra maiúscula, minúscula ou dígito. Use getchar e printf.
  2. ✓ Resposta:
    #include <stdio.h>
    #include <ctype.h>
    
    int main() {
        int c = getchar();
        if (isupper(c)) {
            printf("Maiúscula\n");
        } else if (islower(c)) {
            printf("Minúscula\n");
        } else if (isdigit(c)) {
            printf("Dígito\n");
        } else {
            printf("Outro\n");
        }
        return 0;
    }
    
  3. Exercício 2: Crie um programa que leia um número inteiro e imprima seu quadrado no stdout, e uma mensagem de erro no stderr se o número for negativo.
  4. ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        int num;
        printf("Digite um número: ");
        fflush(stdout);
        scanf("%d", &num);
        if (num < 0) {
            fprintf(stderr, "Erro: número negativo\n");
            return 1;
        }
        printf("Quadrado: %d\n", num * num);
        return 0;
    }
    
  5. Exercício 3: Explique o que acontece quando você executa o comando ./programa 2> erro.log e dê um exemplo de como isso pode ser útil.
  6. ✓ Resposta: O comando redireciona apenas o stderr para o arquivo erro.log. Assim, as mensagens de erro são gravadas no arquivo, enquanto a saída padrão (stdout) continua aparecendo no terminal. Isso é útil para separar logs de erro da saída normal, permitindo analisar erros sem poluir a saída principal.
  7. Exercício 4: Escreva um programa que use fflush para exibir um prompt antes de ler uma string do usuário, e depois imprima a string em stdout.
  8. ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        char nome[100];
        printf("Qual é o seu nome? ");
        fflush(stdout);
        fgets(nome, sizeof(nome), stdin);
        printf("Olá, %s", nome);
        return 0;
    }
    
  9. Exercício 5: Crie um programa que leia dois números e exiba a soma no stdout; se a soma for maior que 100, envie uma mensagem de aviso para stderr.
  10. ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        int a, b;
        printf("Digite dois números: ");
        fflush(stdout);
        scanf("%d %d", &a, &b);
        int soma = a + b;
        printf("Soma: %d\n", soma);
        if (soma > 100) {
            fprintf(stderr, "Aviso: soma maior que 100\n");
        }
        return 0;
    }