⚡ Cap 03: Entrada/Saída Formatada (printf/scanf), Fluxos e Buffers
Bem-vindo ao terceiro capítulo da Especialização em Engenharia de Sistemas com Linguagem C (C17/C23)! ⚡
Todo sistema computacional precisa interagir com o mundo externo: receber entradas do teclado ou sensores, processar esses dados e exibir resultados no monitor ou registrá-los em logs. Em Linguagem C, essas operações são governadas pelos conceitos de Streams (Fluxos de Dados) e Buffers de Terminal.
🗺️ Mapa Conceitual do Capítulo
flowchart TD
A["Entrada e Saída em C (<stdio.h>)"] --> B["1. Os Três Fluxos Padrão"]
A --> C["2. Saída Formatada: printf()"]
A --> D["3. Entrada Formatada: scanf() & &"]
A --> E["4. Buffer do Teclado & '\\n' Residual"]
A --> F["5. Leitura Segura de Strings (fgets)"]
B --> B1["stdin (FD 0), stdout (FD 1), stderr (FD 2)"]
C --> C1["Especificadores (%d, %lf, %s, %p, %zu) e Flags (%.2f, %04d)"]
D --> D1["Operador de Endereço & e Validação de Retorno"]
E --> E1["Solução do Espaço: scanf(' %c') ou loop de limpeza com getchar()"]
F --> F1["Eliminação do gets() e Prevenção de Buffer Overflow (CERT C)"]
📡 1. Os Três Fluxos Padrão (Standard Streams)
Em sistemas operacionais modernos (POSIX e Windows), quando um programa em C inicia, o sistema operacional abre automaticamente três fluxos de dados:
stdin(Standard Input / Descritor 0): Fluxo de entrada padrão. Por padrão, conectado ao teclado.stdout(Standard Output / Descritor 1): Fluxo de saída padrão com buffer (line-buffered). Conectado ao console/terminal.stderr(Standard Error / Descritor 2): Fluxo de saída de erros sem buffer (unbuffered). Garante que mensagens de erro críticas sejam impressas instantaneamente na tela, mesmo se o programa travar logo em seguida.
🖥️ 2. Saída Formatada com printf()
A função printf() converte dados binários da memória em sequências textuais formatadas de acordo com especificadores de conversão:
Tabela de Especificadores e Modificadores de Formato
| Especificador | Tipo de Dado Esperado | Exemplo de Uso | Saída Gerada |
|---|---|---|---|
%d ou %i |
Inteiro com sinal (int, int32_t) |
printf("%d", 42); |
42 |
%u |
Inteiro sem sinal (unsigned int) |
printf("%u", 3000000000U); |
3000000000 |
%f |
Ponto flutuante simples (float) |
printf("%f", 3.14159f); |
3.141590 |
%lf |
Ponto flutuante duplo (double) |
printf("%.2lf", 2499.90); |
2499.90 (2 casas) |
%c |
Caractere individual (char) |
printf("%c", 'A'); |
A |
%s |
Cadeia de caracteres (String) | printf("%s", "Sistemas C"); |
Sistemas C |
%x / %X |
Hexadecimal (minúsculo/maiúsculo) | printf("0x%X", 255); |
0xFF |
%p |
Endereço de memória de ponteiro | printf("%p", (void*)&var); |
0x7ffd5e8a12c4 |
%zu |
Tamanho de memória (size_t) |
printf("%zu", sizeof(int)); |
4 |
Caracteres de Escape Essenciais
\n: Nova linha (Line Feed).\t: Tabulação horizontal (Tab de 4 ou 8 espaços).\\: Imprime uma barra invertida literal\.\": Imprime aspas duplas literais".%%: Imprime o caractere de porcentagem literal%.
⌨️ 3. Entrada Formatada com scanf() e o Operador de Endereço &
Enquanto o printf lê o valor de uma variável, a função scanf precisa saber onde na memória gravar o dado digitado pelo usuário. Por isso, usamos o operador de endereço &:
int idade;
double salario;
// O operador & passa o endereço da variável para o scanf preencher
printf("Digite sua idade e salario: ");
scanf("%d %lf", &idade, &salario);
Validação do Retorno do scanf() (Segurança e Robustez)
O scanf retorna a quantidade de itens lidos com sucesso. Se o usuário digitar letras onde era esperado um número, o scanf falha:
int numero;
printf("Digite um numero inteiro: ");
if (scanf("%d", &numero) != 1) {
fprintf(stderr, "Erro: Entrada invalida! Voce nao digitou um numero.\n");
return 1; // Status de erro
}
⚠️ 4. O Problema do \n Residual no Buffer do Teclado
Quando o usuário digita 42 e pressiona Enter, o buffer de entrada (stdin) recebe três caracteres: '4', '2' e '\n'.
- O
scanf("%d", &num)consome'4'e'2', mas deixa o'\n'preso no buffer. - Quando o programa tenta executar um
scanf("%c", &opcao)logo em seguida, oscanflê imediatamente o'\n'residual e pula a digitação do usuário!
Como Resolver Definitivamente:
- Solução 1 (Espaço em branco antes de
%c): O espaço instrui oscanfa ignorar todos os espaços e quebras de linha anteriores:scanf(" %c", &opcao); // Note o espaço antes de %c - Solução 2 (Função de Limpeza com
getchar()):int c; while ((c = getchar()) != '\n' && c != EOF); // Esvazia o buffer stdin
🛡️ 5. Leitura Segura de Textos e Prevenção de Buffer Overflow (CERT C)
[!CAUTION] A Função
gets()é Extremamente Perigosa: A funçãogets()não verifica o tamanho do vetor de destino e foi banida e removida do padrão oficial ISO C11 por permitir ataques de corrupção de memória (Buffer Overflow).
A Forma Segura com fgets():
char nomeCompleto[50];
printf("Digite seu nome completo: ");
// fgets limita a leitura a no máximo 50 bytes (incluindo o '\0') a partir do stdin
if (fgets(nomeCompleto, sizeof(nomeCompleto), stdin) != NULL) {
printf("Bem-vindo, %s", nomeCompleto);
}
🔍 6. Diagnóstico & Resolução de Problemas (Troubleshooting)
| Sintoma / Erro | Causa Provável | Como Resolver |
|---|---|---|
| O programa trava com Segmentation Fault durante a digitação | Esquecimento do operador & no scanf (ex: scanf("%d", num)). |
Adicione o & antes da variável: scanf("%d", &num);. |
O segundo scanf de caractere é ignorado |
O \n do Enter anterior ficou retido no buffer stdin. |
Adicione um espaço antes do %c: scanf(" %c", &op);. |
| O texto digitado é cortado no primeiro espaço | Uso de scanf("%s", str), que para a leitura no primeiro espaço em branco. |
Utilize fgets(str, sizeof(str), stdin);. |
🏆 7. Desafio Prático de Consolidação
Enunciado do Desafio:
Crie um programa em C chamado cadastro_dispositivo_iot.c que solicite ao usuário:
- O nome do dispositivo (lido com
fgetsde forma segura, até 40 caracteres). - O código de identificação numérico (
uint32_t). - O consumo energético em Watts (
double). - A letra de classificação de eficiência energética (
charde A a F), garantindo que o\nanterior não pule a leitura. - Valide se todas as entradas numéricas foram lidas com sucesso e exiba um relatório final perfeitamente alinhado em colunas com
printf.
🔍 Ver Solução Comentada do Desafio
#include <stdio.h>
#include <stdlib.h>
void limparBufferStdin(void) {
int c;
while ((c = getchar()) != '\n' && c != EOF);
}
int main(void) {
int id;
char categoria;
double voltagem;
printf("====================================================\n");
printf(" 📥 VALIDADOR DE ENTRADA/SAIDA E BUFFERS DE TELEMETRIA\n");
printf("====================================================\n");
printf("Digite o ID do Dispositivo (int): ");
if (scanf("%d", &id) != 1) {
fprintf(stderr, "[ERRO]: ID invalido!\n");
return EXIT_FAILURE;
}
limparBufferStdin();
printf("Digite a Categoria [A, B, C]: ");
categoria = (char)getchar();
limparBufferStdin();
printf("Digite a Voltagem (ex: 12.45): ");
if (scanf("%lf", &voltagem) != 1) {
fprintf(stderr, "[ERRO]: Voltagem invalida!\n");
return EXIT_FAILURE;
}
printf("----------------------------------------------------\n");
printf(" SUCESSO: Dispositivo #%04d | Cat '%c' | Tensao: %+07.2lf V\n",
id, categoria, voltagem);
printf("====================================================\n");
return EXIT_SUCCESS;
}