Title: Nessun titolo diapositiva
1Input e Output in C
2Sommario
- Input e output
- I flussi di I/O
- Luso di buffer
- Apertura e chiusura di file
- Lettura e scrittura di dati
- La selezione di un metodo di I/O
- I/O non bufferizzato
- Laccesso diretto a file
2
3Introduzione
- La gestione dellI/O su file è uno degli aspetti
più complessi dei linguaggi di programmazione,
perché strettamente correlato con le particolari
modalità di accesso a file ed alle periferiche
dettate dal sistema operativo ? difficoltà nella
progettazione di servizi di I/O portabili - Storicamente, lapposita libreria di run?time del
C e la libreria di I/O di UNIX erano parzialmente
sovrapposte tuttavia, la libreria C tratta lI/O
bufferizzato, contrariamente alla libreria UNIX - Le funzioni ANSI di I/O sono tutte bufferizzate
(con possibilità di modifica della dimensione del
buffer) - Le funzioni ANSI di I/O operano inoltre una
distinzione tra accesso a file in modalità
binaria o testuale in ambiente UNIX la
distinzione è poco significativa poiché UNIX
tratta file binari e file di testo alla stessa
stregua ? come sequenze di byte (in altri sistemi
operativi, tale distinzione è invece importante)
4I flussi di I/O ? 1
- Il linguaggio C non distingue tra periferiche e
file su disco in entrambi i casi le operazioni
di I/O sono realizzate attraverso flussi o stream
di I/O associati a file o periferiche - Un flusso di I/O è una sequenza ordinata di byte
la lettura e la scrittura di un file o di una
periferica implicano la lettura/ scrittura di
dati da/su un flusso
5I flussi di I/O ? 2
- Per eseguire lI/O, è necessario associare un
flusso ad un file o a una periferica - occorre dichiarare un puntatore alla struttura
FILE - La struttura FILE, definita in stdio.h, è
costituita da campi che contengono informazioni
quali il nome del file, la modalità di accesso,
il puntatore al prossimo carattere nel flusso - I campi della struttura FILE, istanziati allatto
dellapertura di un flusso (o in fase di
utilizzo), sono dipendenti dallim-plementazione
e differiscono per sistemi operativi diversi
6I flussi di I/O ? 3
- Le strutture FILE forniscono al sistema operativo
le informazioni necessarie per la gestione dei
file lunico meccanismo di accesso ad un flusso
è il puntatore alla struttura FILE, detto
puntatore a file - Il puntatore a file deve essere dichiarato nel
programma, contiene lidentificatore del flusso
restituito da una chiamata alla fopen(), e viene
utilizzato per leggere, scrivere e chiudere il
flusso - Ciascun programma può aprire più flussi
simultaneamente, nel rispetto dei limiti imposti
dalla particolare implemen-tazione
7I flussi di I/O ? 4
- Uno dei campi della struttura FILE è un
indicatore di posizione nel file, che punta al
successivo byte da leggere o scrivere a fronte
di operazioni di lettura/scrittura, il sistema
operativo modifica conseguentemente lindicatore
di posizione - Lindicatore di posizione del file non può essere
manipolato direttamente (in maniera portabile),
ma può essere letto e modificato tramite funzioni
di libreria, permettendo accessi al flusso non
sequenziali - Attenzione il puntatore a file identifica un
flusso aperto, che è connesso ad un file o a una
periferica, lindicatore di posizione nel file
identifica uno specifico byte allinterno di un
flusso
8I flussi standard
- Esistono tre flussi standard, che vengono aperti
automati-camente per ogni programma stdin,
stdout, stderr - I flussi standard sono connessi al terminale, per
default, ma molti sistemi operativi ne permettono
la redirezione (ad es., è possibile inviare
messaggi di errore ad un file per effettuare
diagnostica) - Le funzioni printf() e scanf() utilizzano i
flussi standard di I/O possono essere utilizzate
anche per fare I/O su/da file, ridirigendo stdin
e stdout a file per mezzo della funzione
freopen() - Tuttavia, esistono le funzioni di libreria
apposite, fprintf() e fscanf(), che permettono di
specificare il flusso su cui operare
9I formati testo e binario
- I dati possono essere acceduti in modalità testo
o binaria - Un flusso testuale è composto da una sequenza di
linee, concluse da newline (sistemi operativi
diversi possono memorizzare linee con formati
diversi, utilizzando un carattere differente di
terminazione linea) - I flussi standard sono testuali
- In formato binario il compilatore non effettua
alcuna interpre-tazione dei byte i bit sono
letti e scritti come un flusso continuo - I flussi binari sono utilizzati quando è
fondamentale preservare lesatto contenuto del
file
10Luso del buffer ? 1
- Confrontate con la memoria centrale, le unità di
memoria di massa sono estremamente lente nei
programmi, il tempo richiesto per accedere alle
periferiche eccede largamente il tempo impiegato
dalla CPU per i calcoli - È di fondamentale importanza ridurre il numero di
operazioni di lettura/scrittura, mediante
tecniche di bufferizzazione - Un buffer è unarea di memoria in cui i dati sono
memorizzati temporaneamente, prima di essere
inviati a destinazione - Mediante luso di buffer, il sistema operativo
può limitare il numero di accessi effettivi alla
memoria di massa - Tutti i sistemi operativi utilizzano buffer per
leggere/scrivere su unità di I/O laccesso a
disco, per esempio, avviene con granularità di
blocco, con blocchi di dimensione 512/4096 byte
11Luso del buffer ? 2
- Le librerie di run?time del C contengono un
ulteriore livello di bufferizzazione che può
assumere due forme distinte bufferizzazione a
linee e bufferizzazione a blocchi - Nella bufferizzazione a linee, il sistema
immagazzina i caratteri fino a quando si incontra
un newline (o il buffer è pieno), inviando
lintera linea al sistema operativo (ciò che
accade per linserimento da tastiera) - Nella bufferizzazione a blocchi, il sistema
immagazzina i caratteri fino a riempire un
blocco, trasferendolo quindi al sistema operativo - Tutti i flussi di I/O a file utilizzano una
bufferizzazione a bloc-chi, i flussi riferiti a
terminale sono dipendenti dal sistema operativo,
ma sono o non bufferizzati o bufferizzati a linee
12Luso del buffer ? 3
- Le librerie standard di I/O del C comprendono un
gestore di buffer che mantiene il buffer in
memoria il più a lungo possibile se si accede
alla stessa porzione di un flusso più volte, si
ha alta probabilità che il flusso sia stato
mantenuto nella memoria centrale (si possono
verificare problemi di accesso concorrente,
gestibili via sistema operativo, se il file è
condiviso da più processi) - Sia nel caso di bufferizzazione a righe che a
blocchi, è possibile richiedere esplicitamente al
sistema operativo di forzare linvio del buffer a
destinazione in un momento qualsiasi, per mezzo
della funzione fflush() - Il C consente di personalizzare il meccanismo di
bufferizzazione (modificando le dimensioni del
buffer) fino ad eliminarla, ponendo la dimensione
del buffer a zero
13Il file header stdio.h
- Per utilizzare le funzioni di I/O è necessario
includere il file header stdio.h che contiene - Le dichiarazioni dei prototipi di tutte le
funzioni di I/O - La dichiarazione della struttura FILE
- Le macro costanti, come stdin, stdout, stderr,
EOF - EOF corrisponde al valore restituito dalle
funzioni di I/O in corrispondenza
dellidentificatore di fine file - Nota La definizione di NULL, per lANSI C, è
invece contenuta nel file stddef.h
ifndef NULL define NULL (void ) 0 endif
14La gestione degli errori
?include ltstdio.hgt ?define ERR_FLAG 1 ?define
EOF_FLAG 2 char stream_stat(fp) FILE fp /
se nessun campo booleano è alterato, stat
vale 0 se il solo campo di errore è alterato,
stat vale 1 se il solo campo di fine file è
alterato, stat vale 2 se sono alterati
entrambi, stat vale 3 / char stat
0 if (ferror(fp)) stat
ERR_FLAG if (feof(fp)) stat
EOF_FLAG clearerr(fp) return
stat
- Ogni funzione di I/O restituisce un valore
speciale in caso di errore alcune restituiscono
zero, altre un valore diverso da zero o EOF - Per ogni flusso aperto, esistono due campi
booleani nella struttura FILE che registrano
condizioni di errore o di fine file - Si può accedere ai campi di fine file e di errore
utilizzando le funzioni feof() e ferror() - La funzione clearerr() pone entrambi i campi a
zero
15Apertura e chiusura di file ? 1
- Prima di poter accedere al contenuto di un file,
è necessario aprirlo tramite la funzione fopen(),
che prevede due parametri il nome del file e la
modalità di accesso - Esistono due insiemi di modalità di accesso, per
i flussi testuali e per i flussi binari
r Apre un file testuale esistente in lettura, posizionandosi allinizio del file se il file non esiste, la funzione ritornerà il codice di errore NULL
w Crea un nuovo file testuale e lo apre in scrittura, posizionandosi allinizio del file se il file esiste, i dati precedenti vengono eliminati
a Apre un file testuale esistente in modalità append la scrittura può avvenire solo alla fine del file se il file non esiste verrà creato automaticamente, in caso contrario il contenuto del file preesistente verrà mantenuto
r? Apre un file testuale esistente in lettura e scrittura, posizionandosi allinizio del file se il file non esiste, la funzione ritornerà il codice di errore NULL
w? Crea un nuovo file testuale e lo apre in lettura e scrittura
a? Apre un file testuale esistente o ne apre uno nuovo in modalità append la lettura può avvenire in una posizione qualsiasi del file, la scrittura solo alla fine
16Apertura e chiusura di file ? 2
- Le modalità binarie differiscono per laggiunta
di una b (es., rb) - La funzione fopen() restituisce un puntatore a
file, utilizzabile per accedere successivamente
al file aperto
Proprietà di file e flussi rispetto alle modalità
di apertura della fopen()
r w a r? w? a?
Il file deve esistere prima dellapertura
Il file preesistente viene reinizializzato
Possibilità di lettura del flusso
Possibilità di scrittura sul flusso
Possibilità di scrittura solo alla fine
17Apertura e chiusura di file ? 3
- Esempio Funzione che apre un file testuale,
denominato test, con accesso in lettura - Note
- La funzione fprintf() è analoga alla printf(),
eccettuato un parametro aggiuntivo, che
identifica il flusso di uscita - La funzione open_test() è poco flessibile, perché
permette lapertura del solo file test e soltanto
per accessi in lettura
?include ltstdio.hgt ?include ltstddef.hgt FILE
open_test() / restituisce un puntatore ad una
struttura FILE / FILE fp fp ?
fopen(test, r) if (fp ?? NULL)
fprintf(stderr,Errore nellapertura del file
test\n) return fp
18Apertura e chiusura di file ? 4
?include ltstdio.hgt ?include ltstddef.hgt FILE
open_file(file_name, access_mode) char
file_name, access_mode FILE fp if
((fp ? fopen(file_name, access_mode)) ?? NULL)
fprintf(stderr, Errore nellapertura del
file s \ con modalità di accesso s\n,
file_name, access_mode) return fp
- La funzione open_file() è equivalente alla
fopen(), a meno della notifica esplicita di
errore se il file non può essere aperto
19Apertura e chiusura di file ? 5
- Per chiudere un file viene utilizzata la funzione
fclose(), con argomento il puntatore alla
struttura FILE da chiudere - La chiusura del file provoca il rilascio della
struttura FILE, per la successiva allocazione ad
altri file - La chiusura del file forza anche la scrittura del
contenuto del buffer associato al flusso - Dato che tutti i sistemi operativi stabiliscono
un numero massimo di flussi aperti
contemporaneamente, è buona norma chiudere i file
quando se ne è conclusa lelaborazione - Tutti i flussi aperti vengono comunque chiusi dal
sistema operativo quando il programma termina
correttamente (nel caso di terminazione anomala,
il comportamento non è standardizzato)
20Lettura e scrittura di dati
- Su un file aperto è possibile utilizzare il
puntatore a file per svolgere operazioni di
lettura e scrittura - Le operazioni di accesso al file possono essere
effettuate su oggetti di granularità diversa, in
particolare a livello di - carattere
- linea
- blocco
- Qualsiasi sia la granularità, è impossibile
leggere da un flusso e quindi scrivere sullo
stesso flusso senza che fra le due operazioni
venga effettuata una chiamata a fseek(), rewind()
o fflush() - Le funzioni fseek(), rewind() e fflush() sono le
uniche funzioni di I/O che forzano la scrittura
del buffer sul flusso
21Lettura e scrittura per caratteri ? 1
- Esistono due modalità per la lettura/scrittura di
caratteri da un flusso - getc() macro che legge un carattere da un
flusso - fgetc() analoga a getc(), ma realizzata come
una funzione - putc() macro che scrive un carattere su un
flusso - fputc() analoga a putc(), ma realizzata come
una funzione - Le macro getc() e putc(), in quanto tali, sono
normalmente molto più veloci delle analoghe
funzioni - Tuttavia, se un parametro attuale contiene
operatori che implicano effetti collaterali,
fgetc() ed fputc() sono preferibili - Per le altre macro di libreria, lo standard ANSI
prevede che gli argomenti possano comparire una
sola volta nel corpo della macro, per evitare (il
sommarsi degli) effetti collaterali
22Lettura e scrittura per caratteri ? 2
?include ltstdio.hgt ?include ltstddef.hgt ?define
FAIL 0 ?define SUCCESS 1 int copy_file(infile,
outfile) char infile, outfile FILE
fp1, fp2 if ((fp1 ? fopen(infile, rb)) ??
NULL) return FAIL if ((fp2 ?
fopen(outfile, wb)) ?? NULL)
fclose(fp1) return FAIL while
(!feof(fp1)) putc(getc(fp1), fp2)
fclose(fp1) fclose(fp2) return
SUCCESS
- Esempio Funzione che copia il contenuto di un
file in un altro - Note
- Entrambi i file vengono acceduti in modalità
binaria - La macro getc() legge il prossimo carattere dal
flusso specificato e sposta lindicatore di
posizione del file avanti di un elemento ad ogni
chiamata - In modalità binaria, non è possibile interpretare
il valore di ritorno della getc() per de-cretare
la fine del file feof() invece non presenta
ambiguità
23Lettura e scrittura per linee ? 1
- Esistono due funzioni di I/O orientate alle
linee, fgets() ed fputs() - Il prototipo per la funzione fgets() è
- char fgets(char s, int n, FILE stream)
- Gli argomenti hanno il seguente significato
- s puntatore al primo elemento
dellarray in cui vengono - memorizzati i caratteri letti
- n numero massimo dei caratteri da
leggere - stream puntatore al flusso da cui leggere
- La funzione fgets() legge caratteri fino ad un
newline, la fine del file o il numero massimo
specificato di caratteri, inserendo
automaticamente un carattere \0 dopo lultimo
carattere scritto nellarray restituisce NULL se
incontra la fine del file, altrimenti restituisce
il primo argomento
24Lettura e scrittura per linee ? 2
- La funzione fputs() scrive larray identificato
dal primo argomento nel flusso identificato dal
secondo argomento - La differenza fondamentale fra gets() (che legge
da stdin) ed fgets() è che la seconda inserisce
nellarray (prima del \0 finale) anche il
carattere di newline che delimita la linea - Inoltre fgets() permette la specifica del numero
massimo dei caratteri da leggere, mentre gets()
procede sempre fino ad un terminatore (newline o
EOF)
25Lettura e scrittura per linee ? 3
- Note
- Il file è aperto in modalità testua-le per
accedere ai dati a livello di linea, altrimenti
fgets() potreb-be operare in modo scorretto,
cercando caratteri di newline non presenti nel
file (il terminatore di linea può essere diverso
per si-stemi operativi diversi) - fgets(), invece, si adegua alle caratteristiche
del sistema opera-tivo specifico - La funzione copy_file() per li-nee è più lenta
della versione per caratteri, poiché fgets() ed
fputs() sono realizzate per mezzo di fgetc() ed
fputc() (meno efficienti delle analoghe macro)
?include ltstdio.hgt ?include ltstddef.hgt ?define
FAIL 0 ?define SUCCESS 1 ?define LINESIZE
100 int copy_file(infile, outfile) char
infile, outfile FILE fp1, fp2
char lineLINESIZE if ((fp1 ? fopen(infile,
r)) ?? NULL) return FAIL if ((fp2 ?
fopen(outfile, w)) ?? NULL)
fclose(fp1) return FAIL while
(fgets(line,LINESIZE?1,fp1) !? NULL)
fputs(line, fp2) fclose(fp1) fclose(fp2)
return SUCCESS
26Lettura e scrittura per blocchi ? 1
- Un blocco può essere immaginato come un array
quando si legge o si scrive un blocco è
necessario specificare il numero di elementi del
blocco e la dimensione di ognuno di essi - Le due funzioni orientate alla gestione dei
blocchi sono fread() e fwrite() - Il prototipo per la funzione fread() è
- int fread(void ptr,int size,int nmemb,FILE
stream) - Gli argomenti hanno il seguente significato
- ptr puntatore ad un array in cui vengono
memorizzati i dati - size dimensione di ogni elemento dellarray
- nmemb numero di elementi da leggere
- stream puntatore a file
27Lettura e scrittura per blocchi ? 2
- La funzione fread() restituisce il numero di
elementi effettivamente letti, che dovrebbe
coincidere con il terzo argomento, a meno di
errori o di condizioni di fine file - La funzione fwrite() ha gli stessi argomenti, ma
scrive nel flusso gli elementi contenuti
nellarray - La funzione copy_file() può essere realizzata
anche con granularità di blocco - La condizione di fine file è controllata
confrontando il numero degli elementi letti,
restituito da fread(), con il valore specificato
nella lista degli argomenti se sono diversi si
ha una condizione di fine file o di errore - La funzione ferror() viene utilizzata per
stabilire quale condizione si è verificata
28Lettura e scrittura per blocchi ? 3
while ((num_read?fread(block,sizeof(DATA),
BLOCKSIZE,fp1))?? BLOCKSIZE)
fwrite(block,sizeof(DATA),num_read,fp2)
fwrite(block,sizeof(DATA),num_read,fp2)
if (ferror(fp1)) printf(Errore in
lettura del \ file s\n,infile)
fclose(fp1) fclose(fp2) return
FAIL fclose(fp1) fclose(fp2) return
SUCCESS
?include ltstdio.hgt ?include ltstddef.hgt ?define
FAIL 0 ?define SUCCESS 1 ?define BLOCKSIZE
512 typedef char DATA int copy_file(infile,
outfile) char infile, outfile FILE
fp1, fp2 DATA blockBLOCKSIZE int
num_read if ((fp1 ? fopen(infile, rb)) ??
NULL) printf(Errore nellapertura del
file s \ in input\n, infile)
return FAIL if ((fp2 ? fopen(outfile,
wb)) ?? NULL) printf(Errore
nellapertura del file s \ in
output\n, outfile) fclose(fp1)
return FAIL
29La selezione di un metodo di I/O ? 1
- Le macro putc() e getc() sono le più veloci, ma
la maggior parte dei sistemi operativi è in grado
di realizzare operazioni di I/O su blocchi ancora
più efficienti (ad es., read() e write() in UNIX)
- Talvolta occorre privilegiare la semplicità
allefficienza fgets() e fputs(), ad esempio,
sono lente, ma particolarmente adatte nei casi in
cui sia necessario analizzare linee
?include ltstdio.hgt ?include ltstddef.hgt ?define
MAX_LINE_SIZE 120 int lines_in_file(fp) FILE
fp char bufMAX_LINE_SIZE int
line_num ? 0 rewind(fp) / sposta
lindicatore di posizione
allinizio del file / while
(fgets(buf,MAX_LINE_SIZE,fp)!? NULL)
line_num?? return line_num
Funzione che conta il numero di linee di un file
30La selezione di un metodo di I/O ? 2
- Ultimo, ma non meno importante, fattore da
considerare nella scelta di un metodo di I/O è la
portabilità, fondamen-tale non tanto nella scelta
del tipo di I/O (granularità a caratteri, linee o
blocchi), ma nella scelta della modalità testo o
binaria - Se il file contiene dati testuali (codice
sorgente o documenti), la modalità testo e
laccesso per linee sono da privilegiare - Se i dati sono numerici e non sono strutturati
per linee, è preferibile la modalità binaria, con
accesso per caratteri o per blocchi (codice
eseguibile)
31I/O non bufferizzato ? 1
- Le librerie di run?time del C consentono di
modificare la dimensione del buffer tale
possibilità deve essere utilizzata con
attenzione, dato che la dimensione del buffer
dovrebbe essere ottima per il particolare
sistema operativo - Talvolta, è tuttavia necessario eliminare
completamente la bufferizzazione, tipicamente
quando si vogliono elaborare immediatamente i
dati di input - Per eliminare la bufferizzazione ci si può
avvalere delle funzioni setbuf() e setvbuf() - La funzione setbuf() richiede due parametri un
puntatore a file, ed un puntatore ad un array di
caratteri da utilizzare come nuovo buffer se
tale puntatore è nullo, la bufferizzazione viene
eliminata setbuf() non restituisce valori
setbuf(stdin,NULL)
32I/O non bufferizzato ? 2
- La funzione setvbuf() richiede due parametri
aggiuntivi, che permettono di specificare la
tipologia di bufferizzazione (per linee, per
blocchi, o assente) e la dimensione dellarray da
utilizzare come buffer - La tipologia di bufferizzazione va specificata
mediante uno dei tre simboli definiti in stdio.h - _IOFBF bufferizzazione per blocchi
- _IOLBF bufferizzazione per linee
- _IONBF bufferizzazione assente
- La funzione restituisce un valore diverso da zero
se completata correttamente, zero se non è in
grado di soddisfare la richiesta
setvbuf(stdin,NULL,_IONBF,0)
33Laccesso diretto a file ? 1
- In C, le funzioni per laccesso diretto a file
sono fseek() e ftell() - La funzione fseek() sposta lindicatore di
posizione del file a un carattere specificato nel
flusso - Il prototipo della fseek() è
- int fseek(FILE stream,long int offset,int
whence) - dove
- stream puntatore a file
- offset numero di caratteri di spostamento
- whence posizione di partenza da cui calcolare
lo spostamento - Largomento whence può assumere uno dei tre
seguenti valori, definiti in stdio.h - SEEK_SET inizio del file
- SEEK_CUR posizione corrente dellindicatore
- SEEK_END fine del file
34Laccesso diretto a file ? 2
- Esempio Listruzione
- stat ? fseek(fp,10,SEEK_SET)
- sposta lindicatore di posizione del file al
carattere 10 del flusso (lundicesimo), che sarà
il prossimo elemento letto o scritto - La fseek() restituisce zero se la richiesta è
corretta, un valore diverso da zero altrimenti - Esempio Listruzione
- stat ? fseek(fp,1,SEEK_END)
- non è lecita se fp è aperto in sola lettura,
perché sposta lindicatore oltre la fine del file - Per flussi binari, lo spostamento può essere un
qualsiasi numero intero che non sposti
lindicatore al di fuori del file per flussi
testuali, deve essere zero o un valore restituito
dalla ftell()
35Laccesso diretto a file ? 3
- La funzione ftell() richiede, come unico
argomento, un puntatore a file e restituisce la
posizione corrente dellindi-catore di posizione
nel file - La posizione restituita da ftell() si intende
relativa allinizio del file - per flussi binari rappresenta il numero di
caratteri dallinizio del file alla posizione
corrente - per flussi testuali rappresenta un valore
dipendente dallimplementazione, significativo
solo se utilizzato come parametro per la fseek()
Se la ricerca di una certa stringa nel file
fallisce, lindicatore di posizione nel file
viene riportato al valore originale
cur_pos ? ftell(fp) if (search(string) ?? FAIL)
fseek(fp,cur_pos,SEEK_set)
36Esempio dimensione del file
/ Determinazione del numero di caratteri di un
file con fseek e ftell / ?include
ltstdio.hgt main(int argc, char argv) FILE
fp long n if (argclt2)
printf(File non specificato\n) else
fp?fopen(argv1, rb) / apertura del
file / if (fp !? NULL)
fseek(fp, 0, SEEK_END) / puntatore alla fine
del file / n?ftell(fp) /
lettura posizione del puntatore /
fclose(fp)
printf(Dimensione del file ld\n, n)
else printf(Errore il file s non
esiste, argv1)