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);