Bem-vindo ao décimo oitavo capítulo da Especialização em Engenharia de Sistemas com Linguagem C (C17/C23)! ⚡

A memória RAM é volátil; ao desligar o computador ou encerrar o processo, todos os dados da pilha e do Heap desaparecem. A persistência de dados em armazenamento secundário (SSD, NVMe, Disco Rígido) é essencial para bancos de dados, arquivos de configuração, logs de telemetria e formatos de mídia. Neste capítulo, você dominará o tipo abstrato FILE*, a diferença entre Arquivos Texto e Binários, a serialização direta de estruturas com fread/fwrite e a navegação de cursor com fseek/ftell.

Manipulação de Arquivos, Streams e Persistência Binária em C


🗺️ Mapa Conceitual do Capítulo

graph TD
    A["Persistência em Disco em C"] --> B["1. O Tipo FILE* & fopen/fclose"]
    A --> C["2. Arquivos Texto vs Binários"]
    A --> D["3. Serialização com fread/fwrite"]
    A --> E["4. Navegação fseek & ftell"]

    B --> B1["Modos r, w, a, rb, wb e prevenção de File Descriptor Leaks"]
    C --> C1["fgets/fprintf (Texto) vs bytes brutos sem tradução (Binário)"]
    D --> D1["Gravação direta de structs e arrays com máxima velocidade"]
    E --> E1["SEEK_SET, SEEK_CUR, SEEK_END e cálculo de tamanho de arquivo"]

📁 1. A Abstração de Streams (FILE*) e Abertura Segura

Em C, o acesso a arquivos é gerenciado através de um ponteiro opaco para a estrutura FILE declarada em <stdio.h>. O stream atua como um buffer intermediário em memória RAM entre o seu código e as chamadas de sistema do kernel:

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

int main(void) {
    FILE *fp = fopen("config.txt", "r");

    // Validação OBRIGATÓRIA (CERT C):
    if (fp == NULL) {
        perror("[ERRO]: Falha ao abrir o arquivo");
        return EXIT_FAILURE;
    }

    // Leitura dos dados...

    // Fechamento OBRIGATÓRIO para evitar File Descriptor Leaks:
    fclose(fp);
    fp = NULL;

    return EXIT_SUCCESS;
}

📄 2. Arquivos Texto vs Arquivos Binários

Modo Finalidade Principais Funções Características
Texto ("r", "w", "a") Documentos legíveis por humanos (logs, JSON, CSV). fgets(), fputs(), fprintf(), fscanf() Traduz quebras de linha (\n no Linux $\leftrightarrow$ \r\n no Windows).
Binário ("rb", "wb", "r+b") Arquivos de alta performance (bancos de dados, imagens, structs). fread(), fwrite() Grava uma cópia exata dos bytes da memória RAM sem nenhuma tradução.

⚡ 3. Serialização Binária de Alta Performance com fread e fwrite

Gravar e ler estruturas completas no modo binário é uma das operações mais rápidas em C, pois dispensa qualquer parsing de texto:

typedef struct {
    uint32_t id;
    char sensorNome[32];
    double leitura;
} RegistroSensor;

// 1. GRAVAÇÃO BINÁRIA DIRETA:
RegistroSensor s1 = {101, "Sensor_Pressao", 101.325};
FILE *fEscrita = fopen("sensores.bin", "wb");
if (fEscrita != NULL) {
    fwrite(&s1, sizeof(RegistroSensor), 1, fEscrita);
    fclose(fEscrita);
}

// 2. LEITURA BINÁRIA DIRETA:
RegistroSensor sLido;
FILE *fLeitura = fopen("sensores.bin", "rb");
if (fLeitura != NULL) {
    fread(&sLido, sizeof(RegistroSensor), 1, fLeitura);
    fclose(fLeitura);
    printf("Sensor Lido: #%u - %s (%.3lf)\n", sLido.id, sLido.sensorNome, sLido.leitura);
}

🧭 4. Navegação de Cursor em Disco com fseek() e ftell()

Você pode mover o cursor de leitura/escrita para qualquer byte arbitrário do arquivo:

// Move para o byte 10 a partir do início do arquivo:
fseek(fp, 10, SEEK_SET);

// Move 5 bytes à frente a partir da posição atual:
fseek(fp, 5, SEEK_CUR);

// Move para o final do arquivo:
fseek(fp, 0, SEEK_END);

Cálculo Idiomático do Tamanho de um Arquivo em Bytes:

long calcularTamanhoArquivo(FILE *fp) {
    fseek(fp, 0, SEEK_END);      // Move cursor para o final
    long tamanho = ftell(fp);    // Lê a posição atual em bytes
    rewind(fp);                  // Retorna o cursor para o início (SEEK_SET, 0)
    return tamanho;
}

🛡️ 5. Boas Práticas e Prevenção de Falhas (CERT C)

  1. Prevenção de File Descriptor Leak: O sistema operacional possui um limite máximo de arquivos abertos simultâneos (ex: 1024). Esquecer de chamar fclose(fp) travará o servidor por esgotamento de descritores.
  2. Forçamento com fflush(): Em sistemas críticos ou antes de operações arriscadas, chame fflush(fp) para garantir que os dados do buffer da stdio sejam descarregados fisicamente no disco.
  3. Uso de feof() e ferror(): Não use while (!feof(fp)) como condição de leitura direta; prefira testar o retorno da função de leitura (while (fgets(...) != NULL) ou while (fread(...) == 1)).

🔍 6. Diagnóstico & Resolução de Problemas (Troubleshooting)

Sintoma Observado Causa Provável Como Resolver
fread lê o último elemento duas vezes em um loop O laço utilizou while (!feof(fp)) incorretamente. Controle o laço pelo valor de retorno: while (fread(&reg, sizeof(reg), 1, fp) == 1).
Arquivos binários ficam corrompidos no Windows O arquivo foi aberto em modo texto ("w") em vez de modo binário ("wb"). Sempre adicione a letra 'b' no modo de abertura de arquivos binários.
Dados gravados não aparecem no arquivo imediatamente O buffer de escrita da stdio ainda não foi sincronizado com o disco. Execute fflush(fp) ou feche o arquivo com fclose(fp).

🏆 7. Desafio Prático de Consolidação

Enunciado do Desafio: Desenvolva um programa em C chamado banco_dados_binario.c que implemente um micro banco de dados de clientes com acesso direto (Random Access):

  1. typedef struct { uint32_t id; char nome[40]; double saldo; } Cliente;
  2. bool gravarCliente(const char *arquivo, const Cliente *c); $\rightarrow$ Adiciona registro no final do arquivo binário com "ab".
  3. bool lerClientePorIndice(const char *arquivo, size_t indice, Cliente *c); $\rightarrow$ Utiliza fseek(fp, indice * sizeof(Cliente), SEEK_SET) para ler o $N$-ésimo cliente diretamente em tempo $O(1)$ sem ler os registros anteriores.
  4. Na função main, grave 3 clientes fictícios, leia o 2º cliente diretamente com busca posicional e exiba os dados no console.
🔍 Ver Solução Comentada do Desafio
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>

typedef struct {
    uint32_t id;
    char nome[40];
    double saldo;
} Cliente;

bool gravarCliente(const char *arquivo, const Cliente *c) {
    FILE *fp = fopen(arquivo, "ab");
    if (!fp) return false;
    fwrite(c, sizeof(Cliente), 1, fp);
    fclose(fp);
    return true;
}

bool lerClientePorIndice(const char *arquivo, size_t indice, Cliente *c) {
    FILE *fp = fopen(arquivo, "rb");
    if (!fp) return false;
    fseek(fp, (long)(indice * sizeof(Cliente)), SEEK_SET);
    bool sucesso = (fread(c, sizeof(Cliente), 1, fp) == 1);
    fclose(fp);
    return sucesso;
}

int main(void) {
    const char *db = "clientes_teste.dat";
    Cliente c1 = {101, "Alan Turing", 15000.0};
    Cliente c2 = {102, "Ada Lovelace", 25000.0};

    gravarCliente(db, &c1);
    gravarCliente(db, &c2);

    Cliente lido;
    if (lerClientePorIndice(db, 1, &lido)) {
        printf("Cliente #1 lido com fseek O(1): %s (R$ %.2lf)\n", lido.nome, lido.saldo);
    }
    return 0;
}

🧭 Navegação Rápida

| 📖 Teoria | 📊 Slides | 🧠 Quiz | 💻 Exemplos | 🧩 Exercícios | | :— | :— | :— | :— | :— | | Ler Teoria | Ver Slides | Fazer Quiz | Ver Exemplos | Praticar Exercícios |


🧭 Navegação do Capítulo: ⬅️ Capítulo Anterior · 📚 Sumário do Módulo · ➡️ Próximo Capítulo