Childs: Beh... che facciamo?
R.J.MacReady: Perché... non aspettiamo qui ancora un po'... e vediamo che succede?
(...una premessa: questo post (parte 2 di 3) è un remake di un mio vecchio post. Ma, anche se tratta lo stesso argomento, amplia e perfeziona un po' il discorso è mi è sembrato il caso di riproporlo. Leggete e mi direte...)
Ok, riprendiamo da dove ci eravamo lasciati nell'ultimo articolo (l'avete letto vero?) e, come promesso, questa volta l'argomento sarà una versione con buffered I/O della funzione che esegue la copia di un file (una FileCopy, per gli amici). Ma è doveroso, prima, fornire un altra immagine tratta da The Thing del Maestro John Carpenter , se no potrebbe sembrare che questo sia solo un blog di programmazione, mentre, come ben sapete, è un blog per programmatori cinefili...
![]() |
| ...con un cappello così si programma meglio... |
Allora, ripetiamo: in questa nuova version useremo I/O bufferizzato (e quindi, per esempio, fread(3) invece di read(2)) perché l'obbiettivo questa volta è la portabilità, e cosa c'è di più portabile (nel C) che usare il contenuto di stdio.h? Il codice che vedremo tra un attimo usa lo stesso main() della versione dello scorso articolo e gli unici cambi sono interni alla funzione cpFile(). In realtà, anche la cpFile() è quasi identica da un punto di vista logico, visto che le funzioni buffered hanno una sintassi d'uso e un funzionamento molto simile alle equivalenti versioni unbuffered. Ecco, posso aggiungere che se la versione buffered viene molto diversa (esteticamente e logicamente) dalla unbuffered c'è qualcosa che non va... ma su questo punto farò una breve digressione alla fine del post. Per il momento: vai col codice!
// cpbuffered.c - un cp(1) con buffered I/O#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>// prototipi localioff_t cpFile(const char* src, const char* dest);// funzione main()int main(int argc, char *argv[]){// test argomentiif (argc != 3) {// errore: numero errato di argomenti: escofprintf(stderr, "%s: numero errato di argomenti\n", argv[0]);fprintf(stderr, "uso: %s src dest [e.g.: %s prova.c prova.bak]\n", argv[0], argv[0]);return EXIT_FAILURE;}// eseguo la copiaoff_t n_copied;if ((n_copied = cpFile(argv[2], argv[1])) == -1) {// errore: copia fallita: escofprintf(stderr, "%s: error: %s\n", argv[0], strerror(errno));exit(EXIT_FAILURE);}// esco con Okprintf("%s: copiati %ld byte\n", argv[0], n_copied);return EXIT_SUCCESS;}// cpFile() - copia il file <src> nel file <dest>off_t cpFile(const char *dest, const char *src){// apro il file sorgenteFILE *fp_in;if ((fp_in = fopen(src, "rb")) == NULL) {// ritorno errorereturn -1;}// apro il file destinazioneFILE *fp_out;if ((fp_out = fopen(dest, "wb")) == NULL) {// errore: salvo errno della fopen(3) precedente e chiudo il fileint save_errno = errno;fclose(fp_in);// set errno salvato (evitando quello della fclose(3)) e ritorno erroreerrno = save_errno;return -1;}// r/w loop per la copia usando buffered I/Ooff_t n_copied = 0;size_t n_read;char buffer[BUFSIZ];while ((n_read = fread(buffer, 1, sizeof(buffer), fp_in)) > 0) {// write buffer (con reset prudenziale di errno)errno = 0;if (fwrite(buffer, 1, n_read, fp_out) < n_read) {// errore: salvo errno della fwrite(3) precedente e chiudo i file// NOTA: se errno==0 con scrittura parziale: il disco è pieno (ENOSPC)int save_errno = (errno != 0) ? errno : ENOSPC;fclose(fp_in);fclose(fp_out);// set errno salvato (evitando quello della fclose(3)) e ritorno erroreerrno = save_errno;return -1;}// incremento il contatore di byte copiatin_copied += n_read;}// r/w loop concluso: check finale errori di lettura con ferror(3)if (ferror(fp_in)) {// errore di lettura prima del EOF: salvo errno e chiudo i fileint save_errno = errno;fclose(fp_in);fclose(fp_out);// set errno salvato (evitando quello della fclose(3)) e ritorno erroreerrno = save_errno;return -1;}// raggiunto EOF con successo: check fclose(3) in scrittura per i buffer pendentiif (fclose(fp_out) != 0) {// errore di chiusura fp_out: salvo errno e chiudo anche fp_inint save_errno = errno;fclose(fp_in);// set errno salvato (evitando quello della fclose(3)) e ritorno erroreerrno = save_errno;return -1;}// chiudo fp_in e ritorno i byte copiatifclose(fp_in);return n_copied;}
Ok, come vedete è ampiamente commentato e quindi è auto-esplicativo, per cui non mi dilungherò sulle singole istruzioni e/o gruppi di istruzioni (leggete i commenti! sono li per quello!), ma aggiungerò, solo, qualche dettaglio strutturale. Il main(), come anticipato, è identico a quello visto precedentemente, mentre nella cpFile() si ripetono, esattamente, le spesse operazioni della versione unbuffered, usando però fopen(3) invece di open(2), fclose(3) invece di close(2), ecc. E dove troviamo qualche differenza significativa? Solo nella parte finale, quella dopo il loop while di lettura/scrittura: in questa versione vengono fatti alcuni test sui descrittori di file fp_in e fp_out per verificare che tutto il procedimento sia andato a buon fine: nella versione unbuffered questi test erano impliciti nelle operazioni di read/write (testando se il risultato era uguale a -1), mentre che in questo caso bisogna usare una funzione a parte, ferror(3), e questo perché:
On success, fread() and fwrite() return the number of items read orwritten. This number equals the number of bytes transferred only whensize is 1. If an error occurs, or the end of the file is reached, thereturn value is a short item count (or zero)....fread() does not distinguish between end-of-file and error, and callersmust use feof(3) and ferror(3) to determine which occurred.
Quello sopra è quello che riporta la man-page di fread(3) e fwrite(3), e credo che sia una giustificazione sufficiente sul perché ho scritto il codice così. Quindi, come anticipato, le versioni buffered e unbuffered devono essere quasi sovrapponibili (a parte i test finali), e mi sembra che sia esattamente il risultato raggiunto.
Ed ora la digressione promessa: mi è capitato di trovare in rete (anche in pregevoli blog/siti di programmazione) esempi di implementazione di lettura di file con buffered I/O che usano dei loop di questo tipo:
ecco, come si può commentare questo? Con una sola parola:
NO
Se uno scrive il loop in questa maniera vuol dire che non ha letto la man-page di fread(3) e fwrite(3) o l'ha letta e non ne ha capito il contenuto. Non c'è bisogno di reinventare la ruota, ripeto: fread(3) e fwrite(3) funzionano quasi nello stesso modo di read(2) e write(2), quindi, se l'esempio della cpFile() unbuffered del post precedente era buono (e lo era!), allora l'esempio del post attuale deve risultare (quasi) uguale. Il loop con un while() che testa feof(3) è corretto sintatticamente ma non lo è logicamente, perché inizia testando qualcosa che non è ancora disponibile (uhm, un test predittivo?) e che, oltretutto, non serve testare. Bah, non voglio dilungarmi ulteriormente e vi rimando alla ottima analisi contenuta qui (nel sempre ottimo stackoverflow.com).
Ovviamente spero di non aver offeso nessuno (con la digressione precedente): ricordate, errare humanum est... e, sicuramente, anche in questo blog avrò scritto in passato qualche scemata (spero non grave come quella appena mostrata). Vi assicuro, però, che sto sempre attentissimo a non proporre soluzioni che non ho avuto tempo di scrivere e provare in maniera approfondita, se no invece di un blog di programmazione artistica questo sarebbe un blog di programmazione alla speriamo che funziona...
Ok, per questa seconda puntata quanto detto può bastare. Nella prossima, come promesso nel titolo stesso, vedremo come fare un FileCopy in Go. E sarà interessante, ve lo prometto!
Ciao, e al prossimo post!
