Os fluxos padrão são canais de comunicação pré-definidos entre o seu programa e o ambiente onde ele é executado. Em C, eles são representados por ponteiros do tipo FILE* e são disponibilizados pela biblioteca padrão. Compreender como funcionam é fundamental para escrever programas interativos, processar entrada do usuário e gerar saídas que podem ser redirecionadas ou capturadas por outros programas.

Nesta aula, vamos explorar cada um dos três fluxos padrão, ver como redirecioná-los no shell e entender o mecanismo de buffering, que afeta diretamente a performance e o comportamento dos programas. Além disso, apresentaremos exemplos práticos e exercícios para consolidar o conhecimento.

stdin, stdout, stderr

Todo processo em um sistema operacional do tipo Unix (e também no Windows) tem três fluxos padrão abertos no início da execução: stdin (entrada padrão), stdout (saída padrão) e stderr (erro padrão). Em C, eles são declarados como extern FILE* stdin, extern FILE* stdout e extern FILE* stderr e são definidos no cabeçalho <stdio.h>.

  • stdin: recebe a entrada padrão, normalmente o teclado. Funções como scanf, getchar, fgets (quando usada com stdin) leem dessa fonte.
  • stdout: é a saída padrão, normalmente o terminal. Funções como printf, puts, putchar escrevem nesse fluxo.
  • stderr: é a saída de erro padrão, também normalmente o terminal, mas separada de stdout. Isso permite que mensagens de erro sejam exibidas mesmo quando a saída padrão é redirecionada para um arquivo ou outro programa.

É importante notar que stderr não é bufferizada por padrão (ou é somente com buffer de linha), enquanto stdout é totalmente bufferizada quando não é um terminal. Isso significa que mensagens de erro aparecem imediatamente, enquanto saídas normais podem ficar retidas até o buffer encher ou o programa terminar.

#include <stdio.h>

int main() {
    fprintf(stdout, "Esta mensagem vai para a saída padrão\n");
    fprintf(stderr, "Esta mensagem vai para o erro padrão\n");
    return 0;
}

No código acima, usamos fprintf para escrever explicitamente em cada fluxo. No terminal, ambas as mensagens aparecem, mas se você redirecionar a saída padrão para um arquivo, apenas a primeira mensagem será gravada; a mensagem de erro continuará aparecendo na tela.

Redirecionamento

O redirecionamento é uma funcionalidade do shell que permite controlar de onde o programa lê e para onde escreve. Em sistemas Unix/Linux e no Windows (cmd e PowerShell), os operadores >, < e 2> são usados para redirecionar stdout, stdin e stderr, respectivamente.

  • ./programa > saida.txt – redireciona a saída padrão para o arquivo saida.txt (cria ou sobrescreve).
  • ./programa < entrada.txt – redireciona a entrada padrão para ler de entrada.txt.
  • ./programa 2> erro.txt – redireciona o erro padrão para erro.txt.
  • É possível combinar: ./programa < entrada.txt > saida.txt 2> erro.txt.

Também é possível usar >> para anexar em vez de sobrescrever, e 2>&1 para redirecionar stderr para o mesmo lugar de stdout.

$ ./meu_programa > saida.txt 2> erro.txt
$ cat saida.txt
$ cat erro.txt

No programa C, você não precisa fazer nada especial para que o redirecionamento funcione: ele é transparente, pois o sistema operacional já conecta os fluxos padrão aos arquivos ou dispositivos indicados. Isso torna os programas em C extremamente versáteis para uso em pipelines e automação.

Buffering (fflush)

O buffering é uma técnica que acumula dados em memória antes de escrevê-los no dispositivo de saída, reduzindo o número de chamadas de sistema e melhorando a performance. Em C, o buffer é gerenciado pela biblioteca padrão e pode ser totalmente bufferizado, bufferizado por linha ou não bufferizado, dependendo do tipo de fluxo e das configurações.

Por padrão, stdout é totalmente bufferizado quando redirecionado para um arquivo e bufferizado por linha quando está conectado a um terminal. stderr é não bufferizado (ou bufferizado por linha, dependendo da implementação). Isso significa que, ao usar printf, os dados podem não ser exibidos imediatamente no terminal se o programa terminar abruptamente ou se você esperar uma saída imediata para interação.

A função fflush força a escrita de qualquer dado pendente no buffer. Ela é usada principalmente para garantir que a saída seja enviada imediatamente, por exemplo, antes de uma operação de leitura que depende da saída (como um prompt).

#include <stdio.h>

int main() {
    printf("Digite seu nome: ");
    fflush(stdout); // Garante que o prompt apareça imediatamente
    char nome[50];
    fgets(nome, sizeof(nome), stdin);
    printf("Olá, %s", nome);
    return 0;
}

Em alguns casos, você também pode querer desabilitar o buffering de stdout usando setbuf(stdout, NULL) ou setvbuf, mas isso é menos comum. A função fflush é a ferramenta mais utilizada para controle fino.

Exemplos

Exemplo 1: Lendo e escrevendo com redirecionamento

Vamos criar um programa que lê números do stdin, calcula a soma e escreve o resultado no stdout. Ao redirecionar a entrada e a saída, podemos processar arquivos de dados.

#include <stdio.h>

int main() {
    int num, soma = 0;
    while (scanf("%d", &num) == 1) {
        soma += num;
    }
    printf("%d\n", soma);
    return 0;
}

No shell, podemos usar:

$ echo "1 2 3 4 5" | ./soma
15

Ou com arquivos:

$ ./soma < numeros.txt > resultado.txt

Exemplo 2: Separando erros de saída normal

Um programa que processa um arquivo e reporta erros para stderr, permitindo que o usuário capture apenas os dados úteis no stdout.

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

int main() {
    FILE *fp = fopen("dados.txt", "r");
    if (!fp) {
        fprintf(stderr, "Erro ao abrir arquivo\n");
        exit(1);
    }
    int valor;
    while (fscanf(fp, "%d", &valor) == 1) {
        if (valor < 0) {
            fprintf(stderr, "Valor negativo ignorado: %d\n", valor);
            continue;
        }
        printf("%d\n", valor);
    }
    fclose(fp);
    return 0;
}

Redirecionando saída e erro separadamente:

$ ./processa 2> erros.log > positivos.txt

Exemplo 3: Usando fflush para prompt interativo

Um programa que faz perguntas ao usuário, garantindo que cada prompt seja exibido antes de ler a resposta.

#include <stdio.h>

int main() {
    char resposta[10];
    printf("Você gosta de C? (s/n): ");
    fflush(stdout);
    fgets(resposta, sizeof(resposta), stdin);
    if (resposta[0] == 's' || resposta[0] == 'S') {
        printf("Ótimo!\n");
    } else {
        printf("Que pena!\n");
    }
    return 0;
}

Boas práticas

  • Sempre use stderr para mensagens de erro, nunca stdout, para não misturar dados de saída com erros.
  • Quando precisar que a saída apareça imediatamente (como prompts), chame fflush(stdout).
  • Lembre-se de que stdin e stdout podem ser redirecionados; escreva programas que não dependam de um terminal específico.
  • Verifique o retorno de funções de leitura (como scanf) para tratar entradas inválidas.

Referências

Exercícios

  1. Escreva um programa que leia uma linha do stdin e a imprima três vezes no stdout. Teste com redirecionamento de entrada e saída.

    ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        char linha[100];
        if (fgets(linha, sizeof(linha), stdin) != NULL) {
            for (int i = 0; i < 3; i++) {
                printf("%s", linha);
            }
        }
        return 0;
    }
  2. Crie um programa que leia dois números do stdin e imprima a soma no stdout e a diferença no stderr. Redirecione cada fluxo para um arquivo diferente e verifique o conteúdo.

    ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        int a, b;
        if (scanf("%d %d", &a, &b) == 2) {
            printf("%d\n", a + b);
            fprintf(stderr, "%d\n", a - b);
        } else {
            fprintf(stderr, "Entrada inválida\n");
        }
        return 0;
    }
  3. Explique a diferença entre printf e fprintf(stdout, ...). Escreva um programa que use ambas e mostre que são equivalentes.

    ✓ Resposta:

    Não há diferença prática: printf é equivalente a fprintf(stdout, ...). O programa a seguir demonstra isso.

    #include <stdio.h>
    
    int main() {
        printf("Olá, printf!\n");
        fprintf(stdout, "Olá, fprintf!\n");
        return 0;
    }
  4. Escreva um programa que leia um número e imprima uma mensagem de erro no stderr se o número for negativo, e a mensagem "Positivo" no stdout caso contrário. Use fflush para garantir que a saída apareça imediatamente.

    ✓ Resposta:
    #include <stdio.h>
    
    int main() {
        int n;
        printf("Digite um número: ");
        fflush(stdout);
        if (scanf("%d", &n) != 1) {
            fprintf(stderr, "Erro: entrada inválida\n");
            return 1;
        }
        if (n < 0) {
            fprintf(stderr, "Número negativo\n");
        } else {
            printf("Positivo\n");
        }
        return 0;
    }
  5. Pesquise e explique o que é um pipeline no shell. Crie um programa que leia linhas do stdin, converta para maiúsculas e escreva no stdout. Use-o em um pipeline com cat e grep para filtrar resultados.

    ✓ Resposta:

    Um pipeline conecta a saída de um comando à entrada de outro. O programa abaixo lê de stdin, converte para maiúsculas e escreve em stdout. Exemplo de uso: cat arquivo.txt | ./maiusculas | grep "PALAVRA".

    #include <stdio.h>
    #include <ctype.h>
    
    int main() {
        int c;
        while ((c = getchar()) != EOF) {
            putchar(toupper(c));
        }
        return 0;
    }