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.

Fluxos Padrão e Gerenciamento de Buffers de Entrada e Saída


🗺️ 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:

  1. stdin (Standard Input / Descritor 0): Fluxo de entrada padrão. Por padrão, conectado ao teclado.
  2. stdout (Standard Output / Descritor 1): Fluxo de saída padrão com buffer (line-buffered). Conectado ao console/terminal.
  3. 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'.

  1. O scanf("%d", &num) consome '4' e '2', mas deixa o '\n' preso no buffer.
  2. Quando o programa tenta executar um scanf("%c", &opcao) logo em seguida, o scanf lê 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 o scanf a 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ção gets() 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:

  1. O nome do dispositivo (lido com fgets de forma segura, até 40 caracteres).
  2. O código de identificação numérico (uint32_t).
  3. O consumo energético em Watts (double).
  4. A letra de classificação de eficiência energética (char de A a F), garantindo que o \n anterior não pule a leitura.
  5. 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;
}