Fluxos padrão
Nesta aula, você aprenderá sobre os fluxos padrão da linguagem C (stdin, stdout e stderr), como redirecioná-los no terminal, e como gerenciar o buffer de saída com fflush. Ao final, terá uma compreensão sólida para criar programas mais robustos e depuráveis.
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 comstdin) leem dessa fonte. - stdout: é a saída padrão, normalmente o terminal. Funções como
printf,puts,putcharescrevem 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 arquivosaida.txt(cria ou sobrescreve)../programa < entrada.txt– redireciona a entrada padrão para ler deentrada.txt../programa 2> erro.txt– redireciona o erro padrão paraerro.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.txtNo 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
15Ou com arquivos:
$ ./soma < numeros.txt > resultado.txtExemplo 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.txtExemplo 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
stderrpara mensagens de erro, nuncastdout, 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
stdinestdoutpodem 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
- C IO - cppreference.com
- stdin, stdout, stderr - Linux man page
- Standard Streams - GNU C Library
- stdin, stdout, stderr - POSIX
- fflush - cppreference.com
- C library function fflush
Exercícios
Escreva um programa que leia uma linha do
stdine a imprima três vezes nostdout. 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; }Crie um programa que leia dois números do
stdine imprima a soma nostdoute a diferença nostderr. 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; }Explique a diferença entre
printfefprintf(stdout, ...). Escreva um programa que use ambas e mostre que são equivalentes.✓ Resposta:Não há diferença prática:
printfé equivalente afprintf(stdout, ...). O programa a seguir demonstra isso.#include <stdio.h> int main() { printf("Olá, printf!\n"); fprintf(stdout, "Olá, fprintf!\n"); return 0; }Escreva um programa que leia um número e imprima uma mensagem de erro no
stderrse o número for negativo, e a mensagem "Positivo" nostdoutcaso contrário. Usefflushpara 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; }Pesquise e explique o que é um pipeline no shell. Crie um programa que leia linhas do
stdin, converta para maiúsculas e escreva nostdout. Use-o em um pipeline comcategreppara 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 emstdout. 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; }