Traccia della lezione Lez17: Lucido 3: stderr è distinto da stdout perché si usa stampare i messaggi di errore in modo che compaiano a video, ma si vuole che possano essere reindirizzati indipendentemente dall'uscita vera e propria. I reindirizzamenti dell'ingresso e dell'uscita possono essere presenti entrambi, e in tal caso non conta l'ordine con cui compaiono nella linea di comando. Se la linea di comando contiene delle redirezioni, esse vengono trattate prima di interpretarla e quindi rimosse. La linea di comando: PROGRAM p1 < p2 p3 > p4 p5 viene trattata come segue: - l'input del programma PROGRAM.EXE viene rediretto da tastiera al file p2, cioè si apre tale file (se non esiste, il sistema operativo segnala l'errore) e si eseguono su di esso tutte le operazioni di lettura da tastiera (le chiamate alla funzione "scanf"). - l'output del programma viene rediretto da video al file p4: quando lo eseguiamo, non vedremo nulla a video, ma potremo aprire il file p4 (che sarà un file di testo) per leggere i risultati. - la redirezione dell'ingresso "< p2" e la redirezione dell'uscita "> p4" sono eliminate dalla riga di comando, che diventa semplicemente PROGRAM p1 p3 p5 Quindi i parametri del main per il programma PROGRAM sono: * argc vale 4 * argv[0] vale "PROGRAM" * argv[1] vale "p1" * argv[2] vale "p3" * argv[3] vale "p5" Il programma PARSING_INT.C è una riscrittura dell'analogo programma visto nella lezione precedente, dove i messaggi in uscita e di errore vengono stampati con le funzioni printf(...) e fprintf(stderr,...). Si può usare per sperimentare la redirezione dell'ingresso (mettendo in un file di testo i dati anziché passarli da tastiera), dell'uscita (salvando i risultati su un file di testo) e degli errori (salvando i messaggi di errore su un altro file di testo). Lucido 4: Per confermare la maggior efficienza dei file binari, basta pensare a un numero come 32767, che occupa 16 bit (2 byte) se rappresentato in binario, mentre occupa 5 caratteri, cioè 5 byte se rappresentato come testo. In Windows, la fine di una riga è segnalata da una coppia di caratteri speciali (carriage-return e line-feed), mentre in UNIX si ha solo il carattere line-feed e nelle vecchie versioni di MacOS si ha solo il carattere carriage-return. Sarebbe fastidioso gestire questi caratteri esplicitamente con il linguaggio C, perché si dovrebbe considerare esplicitamente il sistema operativo su cui il programma opera. Invece il carattere '\n' viene automaticamente tradotto dal compilatore nel carattere o nella coppia di caratteri opportuna. Di file binari in questo corso non ci occuperemo perché: - il tempo non è sufficiente - i file di testo vengono usati molto spesso anche nell'industria per rappresentare dati numerici, perché consentono di accedervi anche con semplici editor di testo (l'XML, per esempio, è un formato testuale) - quando i dati sono rappresentati in formato binario (database, spreadsheet, formati proprietari, ecc...), spesso si usano librerie specializzate per leggerli e scriverli, anziché le funzioni standard Lucido 5: La posizione del file (path) può essere indicata esplicitamente, ma di solito conviene indicare la posizione relativa a quella dell'eseguibile: - "prova.txt" indica che il file prova.txt sta nella stessa directory dell'eseguibile - "..\prova.txt" indica che sta nella directory superiore - "dati\prova.txt" indica che sta nella sottodirectory dati A seconda del sistema operativo, occorre usare '\' piuttosto che '/'; questo porta a problemi di portabilita'. Per risolverli, si possono usare costanti simboliche e la compilazione condizionale (vedi la lezione sul preprocessore) per indicare il valore della costante stessa. Alcuni compilatori più sofisticati prevedono la costruzione di un "file di progetto", che specifica quali file .C e .H costituiscono il codice; di solito, in tali casi la posizione relativa si riferisce al file di progetto, anziché all'eseguibile. Quando si usa il file in: - lettura, ci si posiziona all'inizio e non si può scrivervi; - scrittura, ci si posiziona all'inizio e vi si scrive, cancellando quanto conteneva precedentemente; - accodamento, ci si posiziona alla fine e vi si scrive, aggiungendo a quanto conteneva precedentemente. Lucido 6: E' assolutamente opportuno verificare che si sia davvero aperto il file; altrimenti, le istruzioni successive provocherebbero errori e l'uscita dal programma. L'apertura può fallire per vari motivi, fra i quali: - il file non esiste o è in un'altra directory - il file non è accessibile (per esempio, l'accesso è bloccato da altri programmi) - non c'è spazio su disco per creare il file o non si ha il diritto di scrivere in quella zona del disco Lucido 7: In genere, la chiusura di un file non fallisce, per cui è raro che si verifichi il risultato della funzione "fclose". Il valore numerico di EOF è definito in stdio.h (di solito è -1). Perché chiudere i file aperti? - Perché esiste un numero massimo di file simultaneamente apribili da un programma, raggiunto il quale altre aperture falliscono - Perché un file aperto è vulnerabile: se il programma si blocca o il sistema operativo ha dei problemi mentre il file è aperto, i dati potrebbero esserne danneggiati. - Perché molti programmi non possono aprire file aperti da altri programmi (quindi chiudere il file lo rende di nuovo accessibile); - Perché la chiusura chiarisce che il file non è più in uso, e quindi rende il codice più leggibile Il codice CREAFILE.C produce un numero di file indicato dall'utente attraverso la linea di comando, i cui nomi sono PROVA01.TXT, PROVA02.TXT, ecc... Si può osservare: - l'uso di due costanti simboliche di uscita per distinguere errori nella linea di comando o nell'accesso ai file; - l'uso di "prova%03d.txt" per creare i nomi dei file in modo che siano tutti della stessa lunghezza e in ordine alfabetico, senza lasciare spazi bianchi all'interno del nome; - l'assenza della & prima del parametro pn nell'istruzione sscanf(argv[1],"%d",pn), dovuta al fatto che pn è già puntatore alla cella dove va scritto il risultato; - la chiusura dei file via via creati. Della funzione rewind e dell'uso di puntatori multipli allo stesso file vedremo un esempio nella quinta lezione di laboratorio. Lucido 8: Siccome il primo parametro è uno stream fscanf(stdin,...) equivale a scanf(...). Questo può essere utile nel caso di programmi che operano su file o su tastiera secondo i casi: basta usare una variabile: FILE *stream; inizializzarla opportunamente per indicare il caso in cui ci si trova: stream = fp; oppure stream = stdout; e poi usarla, così che funzioni automaticamente in entrambi i casi: fprintf(stream,...). Lucido 9: Se fscanf legge una parte degli oggetti specificati nella stringa di formato e giunge al termine del file prima di completarla, restituisce (come sempre) il numero di oggetti riconosciuti e assegnati. Per verificare che si sia raggiunto il termine del file, occorre allora - chiamare feof e controllare che il risultato sia vero, oppure - chiamare nuovamente fscanf e controllare che il risultato sia EOF N.B.: Se si è esattamente alla fine del file, ma non c'è stato alcun fallimento in lettura, feof restituisce falso. Lucido 10: Per valori di n sufficienti a contenere l'intera riga: - fgets(s,n,fp) legge un'intera riga COMPRESO il carattere '\n' e la assegna a s; - fscanf(fp,"%[^\n]",s) legge un'intera riga ESCLUSO il carattere '\n' e la assegna a s; - fscanf(fp,"%[^\n]\n",s) legge una riga COMPRESO '\n', e la assegna a s ESCLUSO '\n'. Il codice RIGA.C sottolinea le differenze tra i tre comandi, leggendo le prime tre righe da un file indicato dalla linea di comando e stampandole a video. Gli estremi esatti delle stringhe lette sono evidenziati da asterischi. - La lettura con fgets include l'a capo nella stringa s1: infatti, nella stampa l'asterisco compare dopo l'a capo. Poi il codice va a capo un'altra volta per chiarezza. - La lettura con fscanf(fp,"%[^\n]",s) non include l'a capo nella stringa s2 e neppure lo legge: nella stampa l'asterisco compare alla fine della riga senza andare a capo. Infatti, il carattere successivo e' un a capo. - Anche la lettura con fscanf(fp,"%[^\n]\n",s) non include l'a capo nella stringa s2, pero' lo legge: nella stampa l'asterisco compare alla fine della riga senza andare a capo, ma il carattere successivo e' la 'P' che comincia la riga seguente. Questa fscanf non funzionerebbe se non si fosse letto l'a capo precedente (si provi a commentare l'istruzione). Se s è una stringa statica, e quindi sizeof(s) è il numero di caratteri di s, fgets(s,sizeof(s),stdin) equivale a gets(s), salvo che legge e include l'a capo finale, mentre gets(s) lo legge, ma non lo include. La funzione gets() è stata rimossa dagli standard più recenti (C11, del 2011), e sostituita con funzioni più sicure. Ad ogni modo, ci sono ben pochi casi in cui la soluzione migliore non sia la funzione fscanf. Lucido 11: Siccome il primo parametro di fprintf è uno stream, fprintf(stdout,...) equivale a printf(...). Questo può essere utile nel caso di programmi che operano su file o su video secondo i casi: basta definire una variabile FILE *stream; inizializzarla opportunamente per indicare il caso in cui ci si trova: stream = fp; oppure stream = stdout; e poi passarla alla funzione fprintf(stream,...). Il codice STAMPA_QUADRATI.C fa proprio questo: produce i quadrati dei primi 10 numeri interi; se l'utente da linea di fccomando specifica il file in cui salvarli, lo fa. Altrimenti, li stampa a video. Mentre la redirezione dell'uscita consente di salvare su file con risultati che di solito andrebbero stampati a video, questo trucco consente di stampare a video risultati che di solito andrebbero salvati su file. E' anche interessante osservare che cosa cambia fornendo una linea di comando corretta o errata e redirigendo l'uscita: siccome i messaggi di errore sono indirizzati a stderr, non vanno a finire nel file su cui è rediretta l'uscita. STAMPA_QUADRATI stampa il risultato a video STAMPA_QUADRATI prova.txt stampa il risultato sul file prova.txt STAMPA_QUADRATI p1.txt p2.txt stampa un messaggio di errore a video STAMPA_QUADRATI > out.txt stampa il risultato a video, rediretto su out.txt STAMPA_QUADRATI prova.txt > out.txt stampa il risultato sul file prova.txt (out.txt è vuoto) STAMPA_QUADRATI p1.txt p2 > out.txt stampa un messaggio di errore a video, (out.txt è vuoto) STAMPA_QUADRATI p1.txt p2 2> out.txt stampa un messaggio di errore su out.txt Lucido 12: Il meccanismo di buffering è di solito trasparente all'utente, ma può dare effetti fastidiosi durante la scrittura del codice. Se il programma si arresta a causa di un errore, capita spesso che a video o su file non vengano stampati tutti i risultati prodotti sino a qual momento, dato che gli ultimi sono ancora nel buffer di scrittura. A volte questi risultati sarebbero utili per diagnosticare l'errore. Per garantirne la scrittura, si possono aggiungere delle istruzioni fflush in posizioni opportune. Se un programma ha un risultato nel buffer e poi si blocca o rallenta nelle operazioni successive, un'istruzione fflush in mezzo forza la stampa del buffer prima del blocco o rallentamento, rendendo il risultato accessibile all'utente. L'istruzione fflush funziona sui file aperti in scrittura ("w" e "a"). L'effetto dell'istruzione su file aperti in lettura è indefinito.