⚡ Cap 18: Manipulação de Arquivos, Streams e Persistência Binária
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.
🗺️ 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)
- 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. - Forçamento com
fflush(): Em sistemas críticos ou antes de operações arriscadas, chamefflush(fp)para garantir que os dados do buffer da stdio sejam descarregados fisicamente no disco. - Uso de
feof()eferror(): Não usewhile (!feof(fp))como condição de leitura direta; prefira testar o retorno da função de leitura (while (fgets(...) != NULL)ouwhile (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(®, 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):
typedef struct { uint32_t id; char nome[40]; double saldo; } Cliente;bool gravarCliente(const char *arquivo, const Cliente *c);$\rightarrow$ Adiciona registro no final do arquivo binário com"ab".bool lerClientePorIndice(const char *arquivo, size_t indice, Cliente *c);$\rightarrow$ Utilizafseek(fp, indice * sizeof(Cliente), SEEK_SET)para ler o $N$-ésimo cliente diretamente em tempo $O(1)$ sem ler os registros anteriores.- 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