📚 Capítulo do curso: 18 — Manipulação de Arquivos, Streams e Persistência Binária da Especialização em Sistemas com C. Leia o capítulo antes de começar.

💾 Miniprojeto C 18 — Mini SGBD Binário com Acesso Direto

Banco de dados transacional em disco com serialização fread/fwrite e busca posicional.

⏱️ Tempo estimado 📦 Você vai entregar ✅ Pronto quando
55 min main.c + banco.dat gerado o registro de índice 2 é lido no offset 96 e só ele é regravado

Diagrama do 💾 Miniprojeto C 18 — Mini SGBD Binário com Acesso Direto

🎯 Objetivos

📋 O problema

Arquivos de texto são ótimos para humanos, mas ruins para acesso aleatório: para ler a linha 1000 você normalmente precisa ler as 999 anteriores. Arquivos binários de tamanho de registro fixo resolvem isso: se todo registro ocupa exatamente sizeof(struct Registro) bytes, o registro de índice K está sempre no byte K * sizeof(struct Registro) — você pode saltar direto para lá com fseek.

Seu programa (main.c) deve:

  1. Definir struct Registro { int id; char nome[30]; double saldo; };.
  2. Criar 5 registros fixos em memória e gravá-los, em sequência, no arquivo binário banco.dat, usando fopen(..., "wb") e fwrite.
  3. Fechar e reabrir o arquivo em modo "rb+" (leitura e escrita binária, sem truncar).
  4. Usar fseek(arquivo, indice * sizeof(struct Registro), SEEK_SET) para pular diretamente até o registro de índice 2 (terceiro registro), sem ler os registros 0 e 1.
  5. Ler esse registro com fread, imprimir seus campos, e confirmar pelo id que é o registro certo.
  6. Atualizar o campo saldo desse registro (ex: somar 1000.00) e regravar apenas ele no mesmo offset com fseek + fwrite, sem tocar nos outros 4 registros.
ID Requisito
RF01 O programa deve compilar sem nenhum aviso com gcc -Wall -Wextra -Wpedantic -std=c17.
RF02 Deve gravar 5 registros em banco.dat, abrindo o arquivo em modo "wb" e usando fwrite.
RF03 Deve reabrir o arquivo em modo "rb+" após fechar o handle de escrita.
RF04 Deve calcular o offset do registro de índice K como K * sizeof(struct Registro) e usar fseek(..., SEEK_SET) para posicionar o cursor sem ler os registros anteriores.
RF05 Deve ler o registro de índice 2 com fread e validar pelo campo id que é o registro esperado.
RF06 Deve atualizar o campo saldo do registro lido e regravá-lo no mesmo offset (via novo fseek + fwrite), sem regravar o arquivo inteiro.
RF07 Deve validar o retorno de fopen, fseek, fread e fwrite, encerrando com mensagem de erro em caso de falha.
graph LR
    A["fopen('wb')\n+ fwrite x5"] --> B["fclose"]
    B --> C["fopen('rb+')"]
    C --> D["fseek(indice * sizeof(Registro))\nSEEK_SET"]
    D --> E["fread registro alvo"]
    E --> F["atualiza saldo\nem memoria"]
    F --> G["fseek de volta ao\nmesmo offset"]
    G --> H["fwrite so' o registro\natualizado"]

    style A fill:#0284c7,stroke:#38bdf8,color:#ffffff
    style D fill:#7c3aed,stroke:#a78bfa,color:#ffffff
    style H fill:#059669,stroke:#34d399,color:#ffffff

🧰 Ambiente

O mesmo do Miniprojeto 01: gcc 8 ou mais novo. No Windows, rode chcp 65001 se os emojis aparecerem quebrados.


Passo 1 — Compilar o esqueleto

🎯 Objetivo: partir de um código que compila e roda, antes de escrever qualquer linha.

⌨️ Faça: crie a pasta c_mini_18, salve o código abaixo como main.c e rode:

gcc -Wall -Wextra -Wpedantic -std=c17 main.c -o app
./app          # Linux/macOS
.\app.exe      # Windows (PowerShell)

📄 src/esqueleto.c

/*
 * Miniprojeto 18: Mini SGBD Binario com Acesso Direto (fseek O(1))
 * Especializacao em Engenharia de Sistemas em C
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TOTAL_REGISTROS 5
#define NOME_ARQUIVO "banco.dat"

struct Registro {
    int id;
    char nome[30];
    double saldo;
};

int main(void) {
    printf("====================================================\n");
    printf(" ⚡ Miniprojeto 18: Mini SGBD Binario com Acesso Direto\n");
    printf("====================================================\n");

    struct Registro registros[TOTAL_REGISTROS] = {
        {1, "Ana Silva",    1500.00},
        {2, "Bruno Costa",   320.50},
        {3, "Carla Souza",  9800.75},
        {4, "Diego Alves",   150.00},
        {5, "Elisa Nunes",  4200.25}
    };

    printf("\nRegistros em memoria (%zu bytes cada):\n", sizeof(struct Registro));
    for (int i = 0; i < TOTAL_REGISTROS; i++) {
        printf("  [%d] id=%d nome=%s saldo=%.2f\n",
               i, registros[i].id, registros[i].nome, registros[i].saldo);
    }

    // TODO 1: abra NOME_ARQUIVO em modo "wb", valide contra NULL,
    // grave todos os TOTAL_REGISTROS de uma vez com fwrite(), valide o
    // numero de itens gravados, e feche o arquivo com fclose().

    const int indice_alvo = 2;
    printf("\nRegistro alvo: indice %d\n", indice_alvo);

    // TODO 2: reabra NOME_ARQUIVO em modo "rb+" e valide contra NULL.

    // TODO 3: calcule o offset = indice_alvo * sizeof(struct Registro)
    // (use "long" para o tipo do offset) e chame
    // fseek(arquivo, offset, SEEK_SET), validando o retorno (0 = sucesso).

    // TODO 4: declare "struct Registro alvo;", leia com fread() um unico
    // registro a partir da posicao atual do arquivo, valide que leu
    // exatamente 1 item, e imprima id/nome/saldo.

    // TODO 5: some 1000.00 ao campo saldo de "alvo", faca fseek() de
    // volta para o MESMO offset, e grave "alvo" de volta com fwrite()
    // (1 unico item). Feche o arquivo.

    printf("====================================================\n");
    printf(" ✅ Status: Operacional e Validado!\n");
    printf("====================================================\n");

    return EXIT_SUCCESS;
}

✅ Checkpoint: zero avisos do gcc, e a saída é:

🖥️ Saída esperada:

====================================================
 ⚡ Miniprojeto 18: Mini SGBD Binario com Acesso Direto
====================================================

Registros em memoria (48 bytes cada):
  [0] id=1 nome=Ana Silva saldo=1500.00
  [1] id=2 nome=Bruno Costa saldo=320.50
  [2] id=3 nome=Carla Souza saldo=9800.75
  [3] id=4 nome=Diego Alves saldo=150.00
  [4] id=5 nome=Elisa Nunes saldo=4200.25

Registro alvo: indice 2
====================================================
 ✅ Status: Operacional e Validado!
====================================================

Repare no 48 bytes cada: int (4) + char[30] (30) + 6 bytes de padding + double (8). É esse tamanho fixo que vai permitir calcular onde cada registro começa no arquivo.


Passo 2 — Gravar os 5 registros (TODO 1)

🎯 Objetivo: despejar structs inteiras em disco, em binário.

⌨️ Faça: FILE *arquivo = fopen(NOME_ARQUIVO, "wb");, valide contra NULL, grave tudo de uma vez com fwrite(registros, sizeof(struct Registro), TOTAL_REGISTROS, arquivo), confira se o retorno é 5 e feche com fclose. Imprima >> 5 registros gravados em 'banco.dat'.

✅ Checkpoint: banco.dat aparece na pasta com 240 bytes (5 × 48). Confira com ls -l banco.dat (Linux/macOS) ou (Get-Item banco.dat).Length (PowerShell). Você pode apagar a listagem Registros em memoria do esqueleto.

🧯 Se der errado: abrir o arquivo de 240 bytes num editor de texto mostra nomes legíveis no meio de símbolos estranhos: o arquivo é binário (os int e double estão na representação da memória), e isso é o esperado.


Passo 3 — Pular direto para o registro 2 (TODO 2, 3 e 4)

🎯 Objetivo: acesso O(1): calcular a posição em vez de ler os registros anteriores.

⌨️ Faça:

  1. Reabra com fopen(NOME_ARQUIVO, "rb+"), para leitura e escrita sem apagar o conteúdo.
  2. long offset = (long)indice_alvo * (long)sizeof(struct Registro); e fseek(arquivo, offset, SEEK_SET), conferindo se devolveu 0.
  3. struct Registro alvo;, fread(&alvo, sizeof(struct Registro), 1, arquivo), conferindo se leu 1. Imprima o registro e o offset.

✅ Checkpoint: --- Registro lido diretamente no indice 2 (offset=96) --- e id=3 nome=Carla Souza saldo=9800.75.

🧯 Se der errado: se você abriu com "wb+" em vez de "rb+", o arquivo foi truncado para zero bytes e o fread devolve 0.


Passo 4 — Atualizar só esse registro (TODO 5)

🎯 Objetivo: reescrever 48 bytes no meio do arquivo sem tocar no resto.

⌨️ Faça: alvo.saldo += 1000.00;. Depois, volte ao mesmo offset com fseek (o fread avançou o cursor 48 bytes), grave com fwrite(&alvo, sizeof(struct Registro), 1, arquivo) e feche o arquivo.

✅ Checkpoint: >> Registro 3 atualizado: novo saldo = 10800.75, e o arquivo continua com 240 bytes.

🧯 Se der errado: se o arquivo passou a ter o registro 4 corrompido (ou cresceu), faltou o fseek de volta: o fwrite gravou a partir do byte 144, em cima do registro seguinte.


Passo final — Conferir com o gabarito

🎯 Objetivo: validar a saída completa e o código de retorno.

✅ Checkpoint: a saída é esta, e o código de retorno é 0 (echo $? no Linux/macOS ou echo $LASTEXITCODE no PowerShell):

🖥️ Saída esperada:

====================================================
 ⚡ Miniprojeto 18: Mini SGBD Binario com Acesso Direto
====================================================

>> 5 registros gravados em 'banco.dat'

--- Registro lido diretamente no indice 2 (offset=96) ---
id=3 nome=Carla Souza saldo=9800.75

>> Registro 3 atualizado: novo saldo = 10800.75
>> Apenas esse registro foi regravado; os demais 4 ficaram intactos.
====================================================
 ✅ Status: Operacional e Validado!
====================================================

Só abra o gabarito depois de chegar até aqui:

🔑 Ver gabarito: src/main.c

📄 src/main.c

/*
 * Miniprojeto 18: Mini SGBD Binario com Acesso Direto (fseek O(1))
 * Especializacao em Engenharia de Sistemas em C
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TOTAL_REGISTROS 5
#define NOME_ARQUIVO "banco.dat"

struct Registro {
    int id;
    char nome[30];
    double saldo;
};

int main(void) {
    printf("====================================================\n");
    printf(" ⚡ Miniprojeto 18: Mini SGBD Binario com Acesso Direto\n");
    printf("====================================================\n");

    struct Registro registros[TOTAL_REGISTROS] = {
        {1, "Ana Silva",    1500.00},
        {2, "Bruno Costa",   320.50},
        {3, "Carla Souza",  9800.75},
        {4, "Diego Alves",   150.00},
        {5, "Elisa Nunes",  4200.25}
    };

    /* --- Etapa 1: gravar todos os registros no arquivo binario --- */
    FILE *arquivo = fopen(NOME_ARQUIVO, "wb");
    if (arquivo == NULL) {
        fprintf(stderr, "ERRO: nao foi possivel criar %s\n", NOME_ARQUIVO);
        return EXIT_FAILURE;
    }

    size_t gravados = fwrite(registros, sizeof(struct Registro), TOTAL_REGISTROS, arquivo);
    fclose(arquivo);

    if (gravados != (size_t)TOTAL_REGISTROS) {
        fprintf(stderr, "ERRO: gravacao incompleta (%zu de %d registros)\n",
                gravados, TOTAL_REGISTROS);
        return EXIT_FAILURE;
    }
    printf("\n>> %d registros gravados em '%s'\n", TOTAL_REGISTROS, NOME_ARQUIVO);

    /* --- Etapa 2: reabrir e acessar diretamente o registro de indice K --- */
    const int indice_alvo = 2;

    arquivo = fopen(NOME_ARQUIVO, "rb+");
    if (arquivo == NULL) {
        fprintf(stderr, "ERRO: nao foi possivel reabrir %s\n", NOME_ARQUIVO);
        return EXIT_FAILURE;
    }

    /* O offset e' calculado por MULTIPLICACAO, nao por leitura dos
     * registros anteriores: e' isso que torna o acesso O(1) em vez de
     * O(n). Todo registro ocupa exatamente sizeof(struct Registro)
     * bytes, entao o registro "indice_alvo" comeca no byte
     * indice_alvo * sizeof(struct Registro). */
    long offset = (long)indice_alvo * (long)sizeof(struct Registro);
    if (fseek(arquivo, offset, SEEK_SET) != 0) {
        fprintf(stderr, "ERRO: fseek falhou.\n");
        fclose(arquivo);
        return EXIT_FAILURE;
    }

    struct Registro alvo;
    size_t lidos = fread(&alvo, sizeof(struct Registro), 1, arquivo);
    if (lidos != 1) {
        fprintf(stderr, "ERRO: falha ao ler registro de indice %d\n", indice_alvo);
        fclose(arquivo);
        return EXIT_FAILURE;
    }

    printf("\n--- Registro lido diretamente no indice %d (offset=%ld) ---\n",
           indice_alvo, offset);
    printf("id=%d nome=%s saldo=%.2f\n", alvo.id, alvo.nome, alvo.saldo);

    /* --- Etapa 3: atualizar o saldo e regravar somente esse registro --- */
    alvo.saldo += 1000.00;

    /* fread avancou o cursor do arquivo: precisamos voltar ao MESMO
     * offset antes de regravar, senao escreveriamos logo apos o
     * registro lido, corrompendo o proximo. */
    if (fseek(arquivo, offset, SEEK_SET) != 0) {
        fprintf(stderr, "ERRO: fseek de retorno falhou.\n");
        fclose(arquivo);
        return EXIT_FAILURE;
    }

    size_t gravado_unico = fwrite(&alvo, sizeof(struct Registro), 1, arquivo);
    fclose(arquivo);

    if (gravado_unico != 1) {
        fprintf(stderr, "ERRO: falha ao regravar registro atualizado.\n");
        return EXIT_FAILURE;
    }

    printf("\n>> Registro %d atualizado: novo saldo = %.2f\n", alvo.id, alvo.saldo);
    printf(">> Apenas esse registro foi regravado; os demais %d ficaram intactos.\n",
           TOTAL_REGISTROS - 1);

    printf("====================================================\n");
    printf(" ✅ Status: Operacional e Validado!\n");
    printf("====================================================\n");

    return EXIT_SUCCESS;
}

Por que isso importa: esse é exatamente o princípio por trás de índices de bancos de dados reais e de formatos como .wav/.bmp: registros de tamanho fixo permitem acesso aleatório O(1) a qualquer posição do arquivo, sem precisar carregar tudo em memória.


🏆 Desafio

Escreva uma função void listar_todos(const char *nome_arquivo) que reabra banco.dat em modo "rb", use fseek(arquivo, 0, SEEK_END) seguido de ftell(arquivo) para descobrir o tamanho do arquivo em bytes, calcule quantos registros ele contém (tamanho / sizeof(struct Registro)), volte ao início com rewind(arquivo) e imprima todos os registros em um laço — confirmando visualmente que só o registro de índice 2 foi alterado.

✅ Checkpoint: listar_todos encontra 5 registros (240 / 48). Só o id=3 mostra 10800.75, e os outros quatro mantêm o saldo original.

🏁 Desafio integrador

Quando terminar, siga para o Mini-SGBD Binário com Alocação Dinâmica. Ele leva este mesmo motor um passo além: registros alocados com malloc/free, um Makefile e auditoria de vazamentos com Valgrind.


🧭 Navegação

📋 Projetos do curso de C | 📚 Teoria do Capítulo 18 | 🗂️ Hub de projetos