Leitura e escrita de arquivos binários

1. Introdução aos arquivos binários

1.1 Diferença entre arquivos texto e binários

Arquivos texto armazenam dados como caracteres legíveis, usando codificações como ASCII ou UTF-8. Cada número ou valor é convertido em sua representação textual antes de ser gravado. Já arquivos binários armazenam dados exatamente como estão na memória do computador, sem qualquer conversão ou formatação. Enquanto um arquivo texto com o inteiro 12345 ocupa 5 bytes (um para cada caractere), um arquivo binário ocupa apenas 4 bytes (tamanho de um int).

1.2 Quando usar arquivos binários

Arquivos binários são ideais quando:
- Eficiência: leitura e escrita mais rápidas, sem conversões
- Precisão: números de ponto flutuante não sofrem perda por arredondamento em conversões texto
- Dados estruturados: structs e arrays podem ser gravados e lidos em bloco
- Tamanho reduzido: dados ocupam menos espaço em disco

1.3 Modos de abertura

Modo Descrição
"rb" Leitura binária
"wb" Escrita binária (cria/sobrescreve)
"ab" Anexação binária (adiciona ao final)
"rb+" Leitura e escrita binária
"wb+" Leitura e escrita (cria/sobrescreve)

2. Abertura e fechamento de arquivos binários

2.1 Função fopen() para arquivos binários

FILE *arquivo = fopen("dados.bin", "wb");

2.2 Tratamento de erros na abertura

FILE *arquivo = fopen("dados.bin", "rb");
if (arquivo == NULL) {
    perror("Erro ao abrir arquivo");
    return 1;
}

2.3 Função fclose() e boas práticas

if (fclose(arquivo) != 0) {
    perror("Erro ao fechar arquivo");
}

Sempre feche arquivos após o uso para liberar recursos e evitar corrupção de dados.

3. Escrita de dados binários com fwrite()

3.1 Sintaxe e parâmetros

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
  • ptr: ponteiro para os dados a serem escritos
  • size: tamanho de cada elemento em bytes
  • count: número de elementos
  • stream: ponteiro para o arquivo

3.2 Exemplo: escrever structs inteiras

#include <stdio.h>
#include <string.h>

typedef struct {
    int id;
    char nome[50];
    float salario;
} Funcionario;

int main() {
    Funcionario f1 = {1, "Maria Silva", 4500.50};

    FILE *arquivo = fopen("funcionarios.bin", "wb");
    if (arquivo == NULL) {
        perror("Erro ao abrir arquivo");
        return 1;
    }

    size_t escritos = fwrite(&f1, sizeof(Funcionario), 1, arquivo);
    if (escritos != 1) {
        perror("Erro ao escrever dados");
    }

    fclose(arquivo);
    return 0;
}

3.3 Cuidados com alinhamento e padding de structs

O compilador pode adicionar bytes de preenchimento (padding) entre os membros de uma struct para alinhamento de memória. Isso pode causar incompatibilidade ao ler arquivos entre diferentes compilações ou arquiteturas.

// Exemplo de padding
typedef struct {
    char letra;    // 1 byte
    int numero;    // 4 bytes (pode ter 3 bytes de padding após char)
} Exemplo;

printf("Tamanho da struct: %zu bytes\n", sizeof(Exemplo)); // Pode mostrar 8

Para evitar problemas, use #pragma pack(1) ou declare membros em ordem decrescente de tamanho.

4. Leitura de dados binários com fread()

4.1 Sintaxe e parâmetros

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

4.2 Exemplo: ler structs de um arquivo binário

#include <stdio.h>

typedef struct {
    int id;
    char nome[50];
    float salario;
} Funcionario;

int main() {
    Funcionario f;

    FILE *arquivo = fopen("funcionarios.bin", "rb");
    if (arquivo == NULL) {
        perror("Erro ao abrir arquivo");
        return 1;
    }

    size_t lidos = fread(&f, sizeof(Funcionario), 1, arquivo);
    if (lidos == 1) {
        printf("ID: %d\n", f.id);
        printf("Nome: %s\n", f.nome);
        printf("Salário: %.2f\n", f.salario);
    }

    fclose(arquivo);
    return 0;
}

4.3 Verificação do valor de retorno

O valor retornado por fread() indica quantos elementos foram lidos com sucesso. Se for menor que count, pode indicar erro ou fim de arquivo.

size_t lidos = fread(&buffer, sizeof(int), 10, arquivo);
if (lidos < 10) {
    if (feof(arquivo)) {
        printf("Fim do arquivo atingido. Lidos: %zu\n", lidos);
    } else if (ferror(arquivo)) {
        perror("Erro de leitura");
    }
}

5. Posicionamento em arquivos binários

5.1 Função fseek()

int fseek(FILE *stream, long offset, int origem);

Origens disponíveis:
- SEEK_SET: início do arquivo
- SEEK_CUR: posição atual
- SEEK_END: final do arquivo

5.2 Função ftell() para obter a posição atual

long posicao = ftell(arquivo);
printf("Posição atual: %ld\n", posicao);

5.3 Acesso aleatório: lendo e escrevendo em posições específicas

// Pular para o terceiro registro (índice 2)
fseek(arquivo, 2 * sizeof(Funcionario), SEEK_SET);

// Ler o registro na posição atual
fread(&f, sizeof(Funcionario), 1, arquivo);

// Voltar 1 registro a partir da posição atual
fseek(arquivo, -sizeof(Funcionario), SEEK_CUR);

6. Tratamento de erros e fim de arquivo

6.1 Uso de feof() para detectar fim do arquivo

while (!feof(arquivo)) {
    size_t lidos = fread(&f, sizeof(Funcionario), 1, arquivo);
    if (lidos == 1) {
        // Processar registro
    }
}

6.2 Uso de ferror() para verificar erros de I/O

if (ferror(arquivo)) {
    printf("Erro de I/O detectado\n");
    clearerr(arquivo); // Limpa o indicador de erro
}

6.3 Limpeza do buffer de erros com clearerr()

clearerr(arquivo); // Reseta os indicadores de erro e EOF

7. Exemplo prático completo

7.1 Criação de uma struct Aluno

#include <stdio.h>
#include <string.h>

typedef struct {
    char nome[50];
    int idade;
    float notas[3];
} Aluno;

7.2 Escrita de um vetor de alunos em arquivo binário

void escrever_alunos() {
    Aluno alunos[3] = {
        {"João Santos", 20, {8.5, 7.0, 9.2}},
        {"Ana Oliveira", 22, {9.0, 8.5, 7.8}},
        {"Carlos Lima", 19, {6.5, 7.5, 8.0}}
    };

    FILE *arquivo = fopen("alunos.bin", "wb");
    if (arquivo == NULL) {
        perror("Erro ao criar arquivo");
        return;
    }

    fwrite(alunos, sizeof(Aluno), 3, arquivo);
    fclose(arquivo);
    printf("Dados escritos com sucesso!\n");
}

7.3 Leitura e exibição dos dados armazenados

void ler_alunos() {
    Aluno aluno;

    FILE *arquivo = fopen("alunos.bin", "rb");
    if (arquivo == NULL) {
        perror("Erro ao abrir arquivo");
        return;
    }

    printf("\n=== Lista de Alunos ===\n");
    int contador = 1;

    while (fread(&aluno, sizeof(Aluno), 1, arquivo) == 1) {
        printf("\nAluno %d:\n", contador++);
        printf("Nome: %s\n", aluno.nome);
        printf("Idade: %d\n", aluno.idade);
        printf("Notas: %.1f, %.1f, %.1f\n", 
               aluno.notas[0], aluno.notas[1], aluno.notas[2]);
    }

    fclose(arquivo);
}

7.4 Modificação de um registro específico usando fseek()

void atualizar_aluno(int indice, Aluno novo_dado) {
    FILE *arquivo = fopen("alunos.bin", "rb+");
    if (arquivo == NULL) {
        perror("Erro ao abrir arquivo");
        return;
    }

    // Posicionar no registro desejado
    fseek(arquivo, indice * sizeof(Aluno), SEEK_SET);

    // Escrever o novo registro
    fwrite(&novo_dado, sizeof(Aluno), 1, arquivo);

    fclose(arquivo);
    printf("Registro %d atualizado!\n", indice);
}

int main() {
    escrever_alunos();
    ler_alunos();

    // Atualizar o segundo aluno (índice 1)
    Aluno atualizado = {"Pedro Alves", 21, {10.0, 9.5, 9.8}};
    atualizar_aluno(1, atualizado);

    printf("\n=== Após atualização ===\n");
    ler_alunos();

    return 0;
}

Referências