Fine del file: precisazioni su EOF e feof feof

Transcript

Fine del file: precisazioni su EOF e feof feof
Fine del file: precisazioni su EOF e feof
Abbiamo visto, parlando di file di testo, che ci sono due modi per determinare il raggiungimento
della fine del file: EOF e feof. Quando realizziamo archivi su file binari, tuttavia, dobbiamo usarli in
modo diverso.
Supponiamo di voler leggere tutti i record di un archivio, e visualizzarli sul monitor. Dobbiamo
leggere i record uno per uno, fino ad arrivare alla fine del file, e visualizzarli.
feof
Abbiamo visto la funzione feof per determinare il raggiungimento della fine del file. Abbiamo detto
che la funzione restituisce 0 se non è stata raggiunta la fine del file, o un valore diverso da 0 se
siamo alla fine del file. In realtà, le cose stanno in modo leggermente diverso.
Supponiamo che di avere la seguente struttura record:
struct cane
{
char nome[10];
char razza[10];
int eta;
};
typedef struct cane dog;
dog p;
e supponiamo che sul file cani.dat siano archiviati i seguenti record:
Nome
Razza
Anni
Fido
Billy
Jack
Alano
Chiwawa
Doberman
3
2
5
Se andiamo a eseguire il seguente programma per visualizzare sul monitor tutti i record presenti in
archivio,
main()
{
fp=fopen("cani.dat","r");
while(!feof(fp))
{
fread(&p, sizeof(dog),1,fp);
{
printf("nome: %s\n", p.nome);
printf("razza: %s\n", p.razza);
printf("eta: %d\n\n", p.eta);
}
}
fclose(fp);
system("pause");
}
notiamo che sul video otteniamo il seguente risultato:
Cosa è successo? Ci aspettavamo di leggere sul monitor i dati dei tre cani presenti in archivio, e
invece quelli dell’ultimo cane sono stati ripetuti due volte. Il problema sta nel fatto che la funzione
feof non fa esattamente quello che crediamo.
Il sistema operativo (Windows, Mac o Linux) mantiene una serie di informazioni per ogni file
aperto. In particolare, un flag di fine file (end-of-file) viene settato ogni volta che la testina
raggiunge la fine del file, e questo accade solo quando si prova a fare un’operazione di lettura sul
file.
La funzione feof va a controllare lo stato del flag. Se esso è pari a 1, allora la feof restituisce 1 per
dire che ci troviamo alla fine del file, se invece è pari a 0, restituisce 0 per indicare che non siamo
ancora alla fine del file.
Torniamo all’esempio: dopo aver letto i primi tre record, la testina ha si raggiunto la fine del file,
ma il flag di end-of-file non è stato ancora settato (non si è cercato di oltrepassare la fine del file
con un’operazione di lettura). Pertanto, si rientra una quarta volta nel ciclo while. A questo punto,
si cerca di nuovo di effettuare una fread, che ovviamente non riesce perché non ci sono più byte
da leggere sul file, e verrà visualizzato il valore precedente della variabile p.
Solo in questa quarta iterazione, non essendo riuscita l’operazione fread, il flag di end-of-file viene
settato a 1 e si uscirà dal ciclo.
Come risolvere questo problema? Ci sono due possibili soluzioni:
1- Calcolare il numero di record presenti in archivio, e usare un ciclo for anziché un ciclo
while.
2- Controllare il risultato della fread, e visualizzare il record solo se viene effettivamente letto
dal file.
Mostriamo entrambe le possibili soluzioni
Soluzione 1
Per calcolare il numero di record presenti in archivio, procediamo nel seguente modo:
-
posizioniamo la testina alla fine del file
con l’istruzione ftell ci facciamo dire a che numero di byte ci troviamo sul file (in questo
modo, se la testina è alla fine, la ftell ci dice di quanti byte è composto il file).
dividiamo il numero di byte per la dimensione di un record, ottenendo il numero di record.
/* apriamo il file */
fp = fopen("cani.dat","r");
/* posizioniamo la testina alla fine del file */
fseek(fp,0,SEEK_END);
/* calcoliamo il numero di record */
num_byte = ftell(fp);
num_record = num_byte/sizeof(dog);
/* leggiamo uno per uno i record dall'archivio e
li visualizziamo */
for(i=0; i<num_record;i++)
{
fread(&p, sizeof(dog),1,fp);
printf("nome: %s\n", p.nome);
printf("razza: %s\n", p.razza);
printf("eta: %d\n\n", p.eta);
}
fclose(fp);
Soluzione 2
Basta modificare leggermente il codice non funzionante: si visualizza un record solo se la fread è
andata a buon fine. L’istruzione fread per leggere da file binario ha i seguenti parametri:
fread (indirizzo, dim_blocco, num_blocchi, file_pointer)
La fread restituisce il numero di blocchi letti: se è riuscita a leggerli tutti, il numero sarà proprio
num_blocchi, altrimenti un numero minore. Se non è riuscita a leggere nessun blocco, il numero
sarà 0.
main()
{
fp=fopen("cani.dat","r");
while(!feof(fp))
{
if (fread(&p, sizeof(dog),1,fp)!=0)
{
printf("nome: %s\n", p.nome);
printf("razza: %s\n", p.razza);
printf("eta: %d\n\n", p.eta);
}
}
}
fclose(fp);
system("pause");
EOF
Abbiamo visto che EOF è una costante del linguaggio C, che vale -1, e che viene restituita da
un’operazione di lettura su file, come scanf, getc o fread, quando l’operazione non è riuscita
perché è stata raggiunta la fine del file. Anche in questo caso, le cose non stanno esattamente
così. EOF è restituito da funzioni quali la getc e la fscanf, ma non dalla fread!
Come abbiamo appena detto, la fread restituisce il numero di blocchi letti: se è riuscita a leggerli
tutti, il numero sarà proprio num_blocchi, altrimenti un numero minore. Se non è riuscita a leggere
nessun blocco, il numero sarà 0.
Se volessimo scrivere del codice per leggere uno per uno tutti i record dell’archivio e visualizzarli,
come abbiamo fatto prima con la feof, un codice di questo tipo ciclerebbe all’infinito, perché la
fread non restituisce mai EOF.
/* apriamo il file */
fp = fopen("cani.dat","r");
/* leggiamo uno per uno i record dall'archivio e
li visualizziamo */
while(fread(&p, sizeof(dog),1,fp)!=EOF)
{
printf("nome: %s\n", p.nome);
printf("razza: %s\n", p.razza);
printf("eta: %d\n\n", p.eta);
}
fclose(fp);
Potremmo, invece, sfruttare il fatto che la fread restituisce 0 quando non riesce a leggere neanche
un blocco, nel seguente modo:
/* apriamo il file */
fp = fopen("cani.dat","r");
/* leggiamo uno per uno i record dall'archivio e
li visualizziamo */
while(fread(&p, sizeof(dog),1,fp)!=0)
{
printf("nome: %s\n", p.nome);
printf("razza: %s\n", p.razza);
printf("eta: %d\n\n", p.eta);
}
fclose(fp);