File binari

Indice

Introduzione

I file binari non sono file di testo: non contengono righe di caratteri ASCII terminate da ‘\n’, ma sono una generica sequenza di byte “grezzi”, quindi potrebbero contenere dati di qualsiasi tipo: interi, float, stringhe, immagini, suoni.

Nei file binari si parla più propriamente di byte (anche se si usa comunque il termine carattere) e nessuno di questi ha un significato speciale

Record

Quando i byte sono raggruppati in blocchi (ad esempio perche' costituiscono una struttura dati), vengono chiamati record

Ogni record dovrebbe essere letto/scritto in un’unica operazione (tutti i suoi byte insieme)

Un blocco di byte può essere allocato definendo una variabile, un vettore o mediante le funzioni di allocazione dinamica (malloc)

Quando i record hanno tutti la stessa dimensione, l’accesso al record n-esimo è molto veloce in quanto, per determinare il numero del byte da dove esso inizia rispetto all’inizio del file (il suo offset ), basta calcolare: n * L , dove L è la lunghezza dei record in byte, e pozionare lì il file pointer

Si parla allora di file ad acceso diretto, detti anche file ad accesso casuale (file random)

Modalita' di apertura

I file binari si aprono con fopen indicando i modi binari (contengono la lettera b): "rb", "wb", "ab", "r+b" o "rb+", "w+b" o "wb+" e "a+b" o "ab+"

I file binari non sono portabili in quanto i dati composti da più byte possono essere memorizzati in modi diversi su architetture diverse (es. big-endian o little-endian)

Lettura

La lettura di blocchi di byte si ottiene mediante la funzione

fread(p, dim_oggetto, num_oggetti, fp);

legge dal file fp un numero di oggetti pari a num_oggetti (size_t) ciascuno di dimensione pari a dim_oggetto byte (size_t)

e li colloca nel vettore di oggetti puntato da p. La funzione restituisce il numero di oggetti (non di byte) effettivamente letti (può essere inferiore a num_oggetti in caso di errore o fine file).

La funzione fa avanzare il file position pointer del numero di byte effettivamente letti.

Esempio lettura multipla valori interi da file binario

int x[3];

fd = fopen("test.dat", "rb");
if (fd == NULL) {
    //errore
    perror("ERRORE FOPEN");
    exit(1);
}

int n;
while (1) {
    n = fread(x, sizeof(int), 3, fd);
    if (n == 3) {
        printf("ho letto il valore %d %d %d\n", x[0], x[1], x[2]);
    } else {
        printf("ho avuto dei problemi, fread mi ha restituito %d\n", n);
        printf("%d  %d %d\n", x[0], x[1], x[2]);
        break;
    }
}
return 0;

Esempio lettura struct da file binario

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

struct punto {
    int x;
    int y;
    int z;
};

int main() {
    FILE *fd;
    struct punto p;
    int res;

    /* apre il file in lettura */
    fd = fopen("test.dat", "rb");
    if (fd == NULL) {
        perror("Errore in apertura del file");
        exit(1);
    }

    /* ciclo di lettura */
    while (1) {
        res = fread(&p, sizeof(struct punto), 1, fd);
        if (res != 1)
            break;

        printf("coord. punto x:%d y:%d z:%d\n", p.x, p.y, p.z);
    }

    /* chiude il file */
    fclose(fd);

    return 0;
}  

Scrittura

La scrittura di un blocco di byte si ottiene mediante la funzione

fwrite(p, dim_oggetto, num_oggetti, fp);

scrive nel file fp un numero di oggetti pari a num_oggetti (size_t) ciascuno di dimensione pari a dim_oggetto byte (size_t) prelevandoli dal vettore di oggetti puntati da p. La funzione restituisce il numero di oggetti (non di byte) (size_t) effettivamente scritti, può essere inferiore a num_oggetti in caso di errore, fa avanzare il file position pointer del numero di byte effettivamente scritti.

Posizionamento

Le funzioni fread e fwrite posizionano automaticamente il file position pointer (offset) all’inizio del record successivo

Il posizionamento generico si ottiene con la funzione fseek (che annulla l’indicazione eventuale di EOF e svuota il buffer di ungetc)

fseek(fp, offset, origine);

offset (di tipo long) indica su quale carattere, a partire da origine, spostare il file position pointer, negativo per indicare un valore precedente l’origine.

Origine indica da dove calcolare l’offset:

  • SEEK_SET da inizio file
  • SEEK_CURR dalla posizione corrente
  • SEEK_END da fine file (offset negativo)

Esempio

fseek(fp, 20, SEEK_SET);

sposta il file position pointer al 21o carattere del file (il primo ha posizione 0)

Note

Lo spostamento con `fseek` oltre la fine del file non porta al fallimento della operazione (`fseek` restituirà 0).

Se però tenteremo di leggere in quella posizione l'operazione fallirà ed otterremo 0.

Se tenteremo di scrivere in quella posizione l'operazione avrà successo ed avremo creato un file sparso ovvero con dei "buchi".

Lo spostamento con `fseek` prima dell'inizio del file porta ad ottenere un valore di ritorno non zero, che indica il fallimento dell'operazione.

Rilevamento posizione corrente

Per conoscere la posizione attuale del file position pointer (quindi l’offset all'interno del file) si può usare la funzione ftell

posiz = ftell(fp)

posiz è una variabile di tipo long ftell restituisce -1L in caso di errore.

Casi d'uso

  • calcolo dimensione file
  • calcolo numero di record contenuti nel file corrente
  • salvataggio posizione corrente per un successivo ripristino

Calcolo dimensione file

FILE *fp = fopen("test.bin", "rb");
if (fp == NULL) {
    return -1;
}
fseek(fp, 0, SEEK_END); // vado in fondo
long dim = ftell(fp);
printf("ftell mi ha restituito %ld\n", dim);
fclose(fp);

Calcolo numero record file

Assumendo che il file test.bin contenga un certo numero di `struct r` possiamo calcolare QUANTI record sono presenti nel file dividendo la dimensione in byte del file per la dimensione del singolo record.

FILE *fp = fopen("test.bin", "rb");
if (fp == NULL) {
    return -1;
}
fseek(fp, 0, SEEK_END); // vado in fondo
long dim = ftell(fp);
if (dim > 0) {
    numero_record = dim / sizeof(struct r);
}
printf("il numero di record e' %d\n", numero_record);

fclose(fp);

Ritorno ad inizio file

Per tornare all’inizio del file si può usare la funzione `fseek`:

fseek(fp, 0, SEEK_SET);

Ma la soluzione migliore è:

rewind(fp)

Esercizio agenda

Si scriva un programma per gestire un’agenda elettronica. I dati relativi alle persone sono: nome, cognome, indirizzo, telefono, nota, possono contenere spazi e sono organizzati in una struct persona. Si definisca una dimensione massima per il numero di nominativi. Un file binario ad accesso diretto denominato database.dat contiene i record di tutti i nominativi.

Il programma deve fornire un’interfaccia a menu per inserire, cancellare, cercare i dati relativi ad una persona (in base al cognome), visualizzare alfabeticamente tutti i cognomi/nomi senza dettagli. I dati completi siano mantenuti esclusivamente sul file, in memoria si tenga invece un indice alfabetico dei nomi (vettore di strutture contenenti solo cognome, nome e numero del record sul file) per accedere al nominativo.

Validate