Arquivos: fopen, fread, fwrite, fclose

1. Introdução à Manipulação de Arquivos em C

Na linguagem C, arquivos são tratados como fluxos de bytes (streams), independentemente do tipo de dado armazenado. A biblioteca padrão stdio.h fornece um conjunto de funções que permitem abrir, ler, escrever e fechar arquivos de forma eficiente. Antes de explorar as funções específicas, é fundamental entender os dois modos de acesso principais:

  • Modo texto: Os dados são interpretados como caracteres. O sistema pode realizar conversões automáticas (como \n para \r\n no Windows).
  • Modo binário: Os dados são lidos e escritos exatamente como estão na memória, sem qualquer conversão. Indicado para structs, arrays e dados não textuais.

As funções fopen, fclose, fwrite e fread são as ferramentas essenciais para manipulação binária de arquivos, oferecendo controle direto sobre a leitura e escrita de blocos de dados.

2. A Função fopen: Abrindo Arquivos

A função fopen é responsável por estabelecer uma conexão entre o programa e um arquivo no sistema. Seu protótipo é:

FILE *fopen(const char *filename, const char *mode);

O primeiro parâmetro é o nome do arquivo (pode incluir caminho). O segundo parâmetro define o modo de abertura. Os modos mais comuns são:

Modo Descrição
"r" Leitura (texto). O arquivo deve existir.
"w" Escrita (texto). Cria ou sobrescreve.
"a" Anexar (texto). Cria se não existir.
"rb" Leitura binária.
"wb" Escrita binária.
"ab" Anexar binário.
"r+" Leitura e escrita (texto).
"w+" Leitura e escrita (texto). Cria/sobrescreve.
"a+" Leitura e anexar (texto).

O tratamento de erros é crucial: se fopen falhar, retorna NULL. Devemos sempre verificar esse retorno:

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

A função perror exibe uma mensagem descritiva baseada na variável global errno.

3. A Função fclose: Fechando Arquivos

Todo arquivo aberto deve ser fechado para liberar recursos do sistema e garantir que os buffers sejam descarregados. O protótipo é:

int fclose(FILE *stream);

Retorna 0 em caso de sucesso ou EOF (geralmente -1) em caso de erro. Exemplo:

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

Ignorar o fechamento pode causar vazamento de memória, perda de dados (buffers não escritos) e impedir que outros processos acessem o arquivo. Sempre feche arquivos assim que terminar de usá-los.

4. A Função fwrite: Escrevendo Dados Binários

fwrite permite escrever blocos de dados binários diretamente em um arquivo. Seu protótipo:

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

Retorna o número de elementos efetivamente escritos. Exemplo com array:

int numeros[] = {10, 20, 30, 40, 50};
FILE *arquivo = fopen("numeros.bin", "wb");
if (arquivo != NULL) {
    size_t escritos = fwrite(numeros, sizeof(int), 5, arquivo);
    if (escritos != 5) {
        perror("Erro ao escrever");
    }
    fclose(arquivo);
}

Para structs, o uso é igualmente direto:

struct Pessoa {
    char nome[50];
    int idade;
    float altura;
};

struct Pessoa p = {"Maria", 25, 1.68};
fwrite(&p, sizeof(struct Pessoa), 1, arquivo);

5. A Função fread: Lendo Dados Binários

fread é a contraparte de fwrite, lendo blocos binários de um arquivo. Protótipo:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  • ptr: buffer de destino
  • size: tamanho de cada elemento
  • nmemb: número de elementos a ler
  • stream: ponteiro para o arquivo

Retorna o número de elementos lidos. Se for menor que nmemb, pode indicar fim de arquivo ou erro. Exemplo:

int buffer[5];
FILE *arquivo = fopen("numeros.bin", "rb");
if (arquivo != NULL) {
    size_t lidos = fread(buffer, sizeof(int), 5, arquivo);
    if (lidos == 0 && feof(arquivo)) {
        printf("Arquivo vazio ou fim atingido.\n");
    } else if (lidos != 5) {
        perror("Erro de leitura");
    }
    fclose(arquivo);
}

A função feof pode ser usada para verificar se o fim do arquivo foi atingido, enquanto ferror verifica erros de leitura.

6. Exemplo Prático Integrado: Cadastro em Arquivo Binário

Vamos criar um sistema simples de cadastro de alunos usando arquivo binário. Primeiro, definimos a estrutura:

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

#define MAX_ALUNOS 3

struct Aluno {
    int matricula;
    char nome[50];
    float nota;
};

Escrita dos registros:

void escreverAlunos() {
    struct Aluno alunos[MAX_ALUNOS] = {
        {101, "Ana Silva", 8.5},
        {102, "Carlos Souza", 7.2},
        {103, "Beatriz Lima", 9.1}
    };

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

    size_t escritos = fwrite(alunos, sizeof(struct Aluno), MAX_ALUNOS, arquivo);
    if (escritos != MAX_ALUNOS) {
        perror("Erro ao escrever registros");
    }

    fclose(arquivo);
    printf("Dados gravados com sucesso!\n");
}

Leitura dos registros:

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

    printf("\n--- Registros dos Alunos ---\n");
    while (fread(&aluno, sizeof(struct Aluno), 1, arquivo) == 1) {
        printf("Matrícula: %d | Nome: %s | Nota: %.2f\n",
               aluno.matricula, aluno.nome, aluno.nota);
    }

    if (feof(arquivo)) {
        printf("\nFim do arquivo atingido.\n");
    } else if (ferror(arquivo)) {
        perror("Erro durante a leitura");
    }

    fclose(arquivo);
}

Função principal:

int main() {
    escreverAlunos();
    lerAlunos();
    return 0;
}

Ao executar, o programa cria o arquivo alunos.bin, grava três registros e os lê de volta, exibindo os dados na tela. Note como fread é usado em um loop até que retorne 0 (indicando fim do arquivo ou erro).

7. Boas Práticas e Erros Comuns

  • Sempre verifique o retorno de fopen: Usar um ponteiro NULL causa falha de segmentação.
  • Feche arquivos imediatamente: Esquecer fclose pode travar o arquivo para outros processos e causar perda de dados.
  • Cuidado com padding de structs: O compilador pode adicionar bytes de alinhamento entre os campos de uma struct. Isso pode tornar arquivos incompatíveis entre diferentes compiladores ou plataformas. Para evitar problemas, use #pragma pack(1) ou serialize manualmente os campos.
  • Prefira fwrite/fread para dados binários: Funções como fprintf/fscanf convertem dados para texto, o que é mais lento e pode perder precisão em números de ponto flutuante.
  • Use feof e ferror após fread: O retorno de fread sozinho não distingue entre fim de arquivo e erro. Sempre combine com essas funções para diagnóstico correto.
  • Não assuma que fwrite escreverá todos os elementos: Em caso de erro (disco cheio, por exemplo), o retorno será menor que o solicitado. Verifique sempre.

Dominar essas quatro funções é essencial para qualquer programador C que precise trabalhar com persistência de dados binários, sejam eles structs, arrays ou qualquer outro tipo de dado bruto.

Referências