Menu

Gestione dei file in C: fopen, fclose e lettura riga per riga

Come leggere e scrivere file in C: la tabella delle modalità di fopen, il controllo di NULL, scrivere con fprintf e fputc, leggere con fgets, fscanf e fgetc, il ciclo canonico riga per riga e quando usare la modalità binaria.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Tutto quello che un programma calcola sparisce quando termina, a meno che non lo scriva da qualche parte. L'interfaccia per i file del C vive in <stdio.h>, lo stesso header di printf, ed è volutamente simile: un file è un flusso (stream) di byte, e le funzioni che lavorano sullo schermo hanno delle gemelle che ricevono uno stream come argomento.

Il tipo dell'handle è FILE *. Non guardi mai dentro un FILE; è un tipo opaco (vedi typedef) e ne tieni sempre e solo un puntatore.

I tre passaggi

Ogni operazione sui file segue la stessa forma: apri, usa, chiudi.

Il controllo di NULL non è paranoia facoltativa. fopen fallisce ogni volta che il file non esiste (in modalità lettura), la directory non è scrivibile, il percorso è sbagliato o il processo ha esaurito gli handle dei file, e in tutti questi casi restituisce NULL. Usare un handle NULL è un segmentation fault.

Inviare l'errore a stderr invece che a stdout è la convenzione: non è bufferizzato e può essere reindirizzato separatamente dall'output vero.

Modalità di fopen

Il secondo argomento è una breve stringa di modalità. Sbagliarla è l'errore più distruttivo di tutta quest'area, perché "w" svuota in silenzio un file esistente.

ModalitàLetturaScritturaSe il file esisteSe non esiste
"r"sìnosi apre all'iniziofallisce, restituisce NULL
"w"nosìsvuotatocreato
"a"nosìscrive solo in fondocreato
"r+"sìsìsi apre all'iniziofallisce, restituisce NULL
"w+"sìsìsvuotatocreato
"a+"sìsìlegge ovunque, scrive in fondocreato

Aggiungi una b a una qualsiasi di queste ("rb", "wb", "ab+") per la modalità binaria: ne parliamo alla fine.

Due regole che evitano vere perdite di dati:

  • Usa "r" quando vuoi leggere. "r+" su un nome di file sbagliato fallisce in modo sicuro; "w+" crea un file vuoto e hai perso solo tempo. "w" sul nome di file giusto quando intendevi "r" distrugge i dati.
  • Usa "a" per i log. Ogni fprintf finisce in fondo, indipendentemente da dove fosse posizionato lo stream, che è esattamente ciò che vuole un log.

Scrivere e leggere nello stesso programma

L'editor qui sotto esegue un giro completo: crea un file, ci scrive dei record, lo chiude, lo riapre in lettura e stampa ciò che trova.

fprintf e fscanf sono printf e scanf con uno stream come primo argomento; tutto ciò che riguarda i loro specificatori di formato è identico, compresa la larghezza %31s che impedisce a name di traboccare.

La condizione del ciclo è == 2, il numero di elementi richiesti dal formato. Confrontare invece con EOF è un bug classico: una riga malformata fa restituire a fscanf il valore 0, non EOF, e il ciclo gira all'infinito su un input che non riesce a consumare.

Leggere riga per riga con fgets

fscanf è comodo per dati con un formato rigido. Per i file di testo, cioè configurazioni, log, CSV, qualsiasi cosa scritta da una persona, leggi righe intere. Questo è il ciclo canonico:

Cosa rende fgets la scelta predefinita giusta:

  • Riceve la dimensione del buffer, quindi non può traboccare. Passa sizeof line e la chiamata resta corretta anche se ridimensioni l'array.
  • Restituisce NULL a fine file o in caso di errore, il che fornisce una condizione di ciclo pulita.
  • Conserva l'a capo quando la riga è entrata nel buffer. È utile: se in ciò che hai ricevuto non c'è un '\n', la riga era più lunga del buffer e il resto è ancora in attesa. strcspn(line, "\n") trova l'indice dell'a capo (o la lunghezza della stringa se non c'è), quindi assegnare '\0' in quella posizione lo toglie in entrambi i casi.

Per distinguere una vera fine del file da un errore, chiedilo dopo il ciclo:

if (ferror(in)) {
    fprintf(stderr, "errore di lettura\n");
} else if (feof(in)) {
    /* fine normale */
}

Non scrivere while (!feof(fp)) come condizione di ciclo. feof diventa vero solo dopo che una lettura è già fallita, quindi quel ciclo elabora una volta di troppo l'ultimo contenuto del buffer. Verifica invece il valore restituito dalla funzione di lettura, come fanno entrambi i cicli qui sopra.

Un carattere alla volta: fgetc e fputc

Per il lavoro a livello di byte, contare caratteri, trasformare un file, copiarlo, fgetc e fputc gestiscono un carattere per chiamata.

Un dettaglio che conta: c è dichiarato int, non char. fgetc restituisce un int per poter restituire ogni possibile valore di byte e il valore sentinella distinto EOF (che vale -1). Memorizzarlo in un char rende il confronto con EOF sempre falso oppure erroneamente vero per il byte 0xFF, a seconda che char sia signed sulla tua piattaforma. È una delle trappole più antiche del C.

Controllare gli errori come si deve

Una lettura da codice di produzione ha questo aspetto:

errno contiene un codice che descrive l'ultimo fallimento e strerror lo trasforma in una frase; perror stampa il tuo messaggio più quella frase in una sola chiamata. Richiedono rispettivamente <errno.h> e <string.h>.

Anche fclose può fallire, perché svuota i dati bufferizzati e la scrittura potrebbe non entrare sul disco, quindi per tutto ciò che è importante controllalo:

if (fclose(fp) != 0) {
    fprintf(stderr, "impossibile svuotare il buffer e chiudere\n");
}

La modalità binaria in un paragrafo

La modalità testo può tradurre i fine riga (su Windows \n diventa \r\n in scrittura e torna com'era in lettura) e può trattare alcuni byte in modo speciale. Per dati che non sono testo, un'immagine, una struct scaricata così com'è, un blob compresso, apri con b e usa fread/fwrite, che spostano byte grezzi:

fwrite(ptr, size, count, fp) scrive count elementi di size byte e restituisce quanti ne è riuscita a scrivere; fread fa il contrario. Tieni presente che un file scritto così è legato alla macchina che l'ha scritto, perché padding delle struct, dimensione degli interi e ordine dei byte finiscono tutti nei byte: va bene per una cache o un file temporaneo ed è sbagliato per un formato che altri programmi devono leggere.

Errori comuni

  • Non controllare che fopen non restituisca NULL. Il crash che segue viene attribuito alla lettura, non all'apertura.
  • Aprire con "w" quando intendevi "r". Il file viene svuotato prima che tu te ne accorga.
  • Dimenticare fclose. L'output bufferizzato va perso e ogni file non chiuso spreca un handle.
  • while (!feof(fp)). Elabora due volte l'ultima riga. Verifica invece la chiamata di lettura.
  • char c = fgetc(fp). Rompe il confronto con EOF. Usa int.
  • fscanf("%s", buf) senza larghezza. Lo stesso buffer overflow di scanf sulla tastiera.
  • Percorsi relativi. fopen("data.txt", "r") cerca nella directory di lavoro, non accanto all'eseguibile. Se un file "sparisce", di solito il motivo è questo.

Domande frequenti

Come si apre un file in C?

FILE *fp = fopen("data.txt", "r"); apre un file in lettura e restituisce un handle FILE *, oppure NULL se l'apertura è fallita. Controlla sempre NULL prima di usare l'handle e chiama fclose(fp) quando hai finito.

Quali sono le modalità di fopen in C?

"r" lettura (il file deve esistere), "w" scrittura (crea il file, oppure svuota un file esistente), "a" aggiunta (crea il file, scrive in fondo). Aggiungere + rende ciascuna di lettura e scrittura: "r+", "w+", "a+". Aggiungere b ("rb", "wb") apre in modalità binaria.

Come si legge un file riga per riga in C?

Usa fgets in un ciclo while: while (fgets(line, sizeof line, fp) != NULL) { ... }. Si ferma a ogni a capo o quando il buffer è pieno, restituisce NULL a fine file e non può traboccare perché gli passi la dimensione del buffer.

Perché il mio file è vuoto dopo averci scritto in C?

Il più delle volte hai dimenticato fclose. L'output è bufferizzato, quindi i dati possono essere ancora in memoria quando il programma termina in modo anomalo. fclose svuota il buffer e chiude; fflush(fp) svuota il buffer senza chiudere. L'altra causa è aprire una seconda volta con "w", che svuota il file appena scritto.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA