Istruzioni di I/O


Dal punto di vista del linguaggio C un file è una sequenza di byte senza alcuna strutturazione. Il C fornisce, tramite la libreria del linguaggio, una grande varietà di istruzioni di input/output (brevemente I/O). Nello standard ANSI sono presenti una quarantina di diverse istruzioni di I/O. In genere, l' I/O C  avviene attraverso un buffer,  un' area di memoria intermedia  che funge da contenitore temporaneo dei byte in attesa che questi vengano letti (in input) o scritti (in output). La presenza del buffer come contenitore temporaneo dei dati permette di gestire meglio, in modo asincrono, le lente operazioni  di I/O da parte del sistema operativo, migliorando l' efficienza complessiva dell' elaborazione. Tuttavia,  in caso di necessità,  il programmatore può  intervenire sulla dimensione e sullo svuotamento del buffer.
Lo standard  ha inoltre introdotto la distinzione tra files binari e di testo, a volte ridondante (p.es. sui sistemi UNIX), ma che aumenta la portabilità dei programmi.

Il principale concetto dell' I/O del C  è che questo avviene mediante facendo riferimento non a file ma a , flussi di dati (stream), associati  a files o devices (periferiche). Uno stream consiste in una sequenza ordinata di bytes. Ogni operazione di lettura o scrittura su file corrisponde a leggere o scrivere dati dallo  o sullo stream associato a quel file.

La prima operazione necessaria per ogni operazione di lettura/scrittura è dunque associare uno stream ad un file o ad una periferica di I/O.
Nell' I/O con buffer questa associazione è fatta tramite pointer a struttura di tipo FILE.
FILE è una struttura, definita in stdio.h, contenente diversi campi per  informazioni quali : nome del file, modo di accesso, puntatori al prossimo carattere dello stream. Tuttavia, dal punto di vista del programmatore non è necessario conoscere i dettagli interni della struttura FILE.

Ci sono sempre 3 streams connessi permanentemente con un processo:   stdin,   stdout,   stderr.

Usualmente, in un' interazione  diretta col computer, stdin punta alla tastiera, mentre stdout e stderr allo schermo. E' però possibile ridefinire questi simboli associandoli per esempio a file su disco.

Aprire e chiudere un file.


Function fopen("nomefile","modo"). "nomefile" e' una stringa che rappresenta il nome del nuovo file, mentre "modo" puo' essere:
 



"r" Apre un file esistente in lettura
"a" Apre un file esistente in modo "append". La scrittura e' sempre alla posizione dell end of file
"w" Crea un nuovo file in scrittura
"r+" lettura e scrittura
"w+" scrittura e lettura
"a+" scrittura in modo append e lettura

Inoltre,  per ciascuno di questo modi,  puo' essere specificato che va applicato   a file "binari" aggiungendo una b dopo la codifica del modo. Su sistemi UNIX non c' e' differenza tra file di testo e binari dal  punto di vista della  function fopen.

Con i modi "r" e r+" il file deve esistere.

Con i modi "w" e w+" il file viene creato se non esiste ma ne viene cancellato il contenuto se già esiste e non è vuoto.


fopen ritorna un puntatore al tipo FILE (struttura) che va utilizzato per accedere al file con le operazioni di lettura e scrittura. Se c' è un errore nell' aperture del file (p.es; un file in lettura che non esiste) fopen ritorna il puntatore NULL.

Un file aperto può essere chiuso col comando fclose(fp) che chiude il file associato al puntatore fp. La chiusura del file libera la struttura FILE a cui fp punta. Inoltre provoca lo svuotamento di ogni buffer associato allo stream concludendone le operazioni di I/O. Tutti i files di un programma che termina normalmente sono automaticamente chiusi.
 

Functions  di I/O


Le operazioni di I/O possono essere basate su tre diversi livelli: a carattere, a linea e a blocco.
Le istruzioni di lettura/scrittura a carattere sono:
 



int getc(FILE *stream)
getchar(void)
legge un carattere dallo stream (usualmente una macro) ritornandolo come  int o EOF se raggiunge la fine del file.  getchar legge da stdin
int putc(int c, FILE *stream) scrive un carattere  (usualmente una macro). Ritorna il carattere scrittto o EOF se si è verificata una condizione di errore.
int fgetc(FILE *stream) legge un carattere dallo stream (function)
int fputc(int c, FILE *stream) scrive un carattere  (function)

Lettura e scrittura a righe:
 



char *fgets(char *s, int size, FILE *stream),
char *gets(char *s)
legge una sequenza di caratteri dallo stream fino al carattere newline (\n) o ad un massimo numero (size) di caratteri e lo assegna ad una stringa.
gets opera come fgets ma legge stdin, non include il carattere newline sulla stringa e non permette di leggere un numero predefinito di caratteri. A causa dei problemi legati all' assenza di controllo sul numero dei caratteri letti il suo uso è sconsigliato.
int fputs(const char *s, FILE *stream),
int puts(const char *s)
scrive una stringa sullo stream senza il terminatore di stringa (\0) Ritorna EOF in caso di errore

Mediante queste funzioni primitive di lettura/scrittura si possono ottenere istruzioni di I/O piu' complesse aggiungendo eventuali conversioni di sequenze di caratteri nelle rappresentazioni interne dei vari tipi dati. Alcune di queste possibilita' di I/O e conversione sono svolte da alcune function di libreria:
 



int scanf(const char *format, ...) legge e converte in accordo ai convertitori di formato presenti nella stringa format  le variabili  da stdin. Ritorna il numero delle variabili assegnate con successo.
int fscanf(FILE *stream, const char *format, ...) legge e converte in accordi ai convertitori di formato  le variabili presenti sullo stream indicato come primo argomento
int sscanf(const char *str, const char *format, ...) legge e converte in accordi ai convertitori di formato  le variabili presenti sulla stringa str 
int printf(const char *format, ...) converte e scrive su stdout in accordo ai convertitori di formato le variabili nella lista (se presenti) nelle posizioni caratterizzate dei convertitori di formato nella stringa  il valore intero di ritorno e' il numero di caratteri stampati
int fprintf(FILE *stream, const char *format, ...) come printf ma su file
int sprintf(char *str, const char *format, ...) come printf ma su stringa
int snprintf(char *str, size_t size, const  char  *format,...) come sprintf ma scrive al piu' size caratteri (incluso il terminatore di stringa \0)

La stringa di formato contiene caratteri e convertitori di formato. Nella printf  e affini, i caratteri sono interpretati come costanti letterali che vanno scritte in  output.
Nella scanf e affini, i caratteri <spazio> e <tab> sono interpretati come "qualsiasi sequenza di uno o piu'  separatori tra dati (a meno che non ci sia un convertitore a carattere, nel qual caso uno spazio viene interpretato come il carattere corrispondente. Altri caratteri indicano costanti letterali che devono essere presenti nello stream di input, pena un errore in lettura.

Esempio: l' istruzione scanf("Numero : %d Giorno : %s",x,giorno);   permette di leggere la seguente linea di input:

Numero : 13 Giorno : venerdi'

assegnando alle variabili x e giorno i valori 13 e "venerdi'". Se tuttavia un campo di input  fosse diverso anche per un solo carattere dalla  sequanza di caratteri nel format, la lettura terminerebbe in corrispondenza di quel campo.

Lettura e scrittura a blocchi.

size_t fread ((void *) restrict ptr, size_t size, size_t nmemb, FILE * restrict stream)
Legge,  in un blocco che inizia alla posizione indicata da ptr, nmemb dati di dimensione size dallo stream. Ritorna il numero di dati letti realmente
size_t fwrite (const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict stream)
Scrive i dati nel blocco che inizia alla posizione indicata da ptr in uno stream



Istruzioni ausiliarie


Alcune functions della libreria C permettono di gestire e controllare l' I/O. Per la spiegazione degli argomenti e del modo di uso,  fare riferimento alle corrispondenti man page.
 
 

int setvbuf(FILE *stream, char *buf, int mode , size_t size)
permette di modificare il tipo (senza b.,  orientato al blocco o alla linea), la dimensione e la localizzazione del buffer
int ungetc(int c,FILE *stream) rimette il carattere c sullo stream. Una successiva lettura di un carattere ritornerebbe c. L' indicatore di posizione sul file viene decrementato di uno.
int fflush(FILE *stream) forza la scrittura di tutti i dati del buffer relativo allo stream di output stream. Ritorna 0 se ha successo,EOF altrimenti. Non andrebbe usata su stream di input in quanto il risultato non è univocamente determinato dallo standard.
int fseek( FILE *stream, long offset, int whence) modifica l' indicatore di posizione  per lo stream in argomento aggiungendo offset alla posizione indicata con whence
long ftell( FILE *stream) ottiene il valore corrente dell' indicatore di posizione per lo stream in argomento
 void rewind( FILE *stream) riporta all' inizio l' indicatore di posizione dello stream in argomento 
int feof( FILE *stream) verifica la condizione di end of file per lo stream in argomento
void clearerr( FILE *stream) azzera la condizione di errore o di end of file per lo stream in argomento