Si prosegue con l'I/O binario
[gapil.git] / filestd.tex
index c0a628312bc2a3d415a81c95cb2ad737d9825077..a20d207aea09d97e4b69dfb9fd73ef0715b80d83 100644 (file)
@@ -1,4 +1,4 @@
-\chapter{I file: l'interfaccia standard ANSI C}
+ \chapter{I file: l'interfaccia standard ANSI C}
 \label{cha:files_std_interface}
 
 Esamineremo in questo capitolo l'interfaccia standard ANSI C per i file,
@@ -83,81 +83,87 @@ file \file{stdio.h}.
 \subsection{Gli stream standard}
 \label{sec:file_std_stream}
 
-Ai tre file descriptor standard (vedi \secref{sec:file_std_descr}) aperti per
-ogni processo, corrispondono altrettanti stream, che rappresentano i canali
-standard di input/output prestabiliti; anche questi tre stream sono
-identificabili attraverso dei nomi simbolici definiti nell'header
-\file{stdio.h} che sono:
+Ai tre file descriptor standard (vedi \secref{sec:file_std_descr})
+aperti per ogni processo, corrispondono altrettanti stream, che
+rappresentano i canali standard di input/output prestabiliti; anche
+questi tre stream sono identificabili attraverso dei nomi simbolici
+definiti nell'header \file{stdio.h} che sono:
 
 \begin{itemize}
-\item \var{FILE * stdin} Lo \textit{standard input} cioè lo stream da cui
-  il processo riceve ordinariamente i dati in ingresso. Normalmente è associato
-  dalla shell all'input del terminale e prende i caratteri dalla tastiera.
-\item \var{FILE * stdout} Lo \textit{standard input} cioè lo stream su cui
-  il processo invia ordinariamente i dati in uscita. Normalmente è associato
-  dalla shell all'output del terminale e scrive sullo schermo.
-\item \var{FILE * stderr} Lo \textit{standard input} cioè lo stream su cui
-  il processo è supposto inviare i messaggi di errore. Normalmente anch'esso
-  è associato dalla shell all'output del terminale e scrive sullo schermo.
+\item \var{FILE * stdin} Lo \textit{standard input} cioè lo stream da
+  cui il processo riceve ordinariamente i dati in ingresso. Normalmente
+  è associato dalla shell all'input del terminale e prende i caratteri
+  dalla tastiera.
+\item \var{FILE * stdout} Lo \textit{standard input} cioè lo stream su
+  cui il processo invia ordinariamente i dati in uscita. Normalmente è
+  associato dalla shell all'output del terminale e scrive sullo schermo.
+\item \var{FILE * stderr} Lo \textit{standard input} cioè lo stream su
+  cui il processo è supposto inviare i messaggi di errore. Normalmente
+  anch'esso è associato dalla shell all'output del terminale e scrive
+  sullo schermo.
 \end{itemize}
 
-Nelle \acr{glibc} \var{stdin}, \var{stdout} e \var{stderr} sono effettivamente
-tre variabili di tipo \type{FILE *} che possono essere usate come tutte le
-altre, ad esempio si può effettuare una redirezione dell'output di un
-programma con il semplice codice:
+Nelle \acr{glibc} \var{stdin}, \var{stdout} e \var{stderr} sono
+effettivamente tre variabili di tipo \type{FILE *} che possono essere
+usate come tutte le altre, ad esempio si può effettuare una redirezione
+dell'output di un programma con il semplice codice:
 \begin{lstlisting}[labelstep=0,frame=,indent=1cm]{}
     fclose (stdout);
     stdout = fopen ("standard-output-file", "w");
 \end{lstlisting}
-ma in altri sistemi possono essere definite come macro, se si hanno problemi
-di portabilità e si vuole essere sicuri, diventa opportuno usare la funzione
-\func{freopen}.
+ma in altri sistemi possono essere definite come macro, se si hanno
+problemi di portabilità e si vuole essere sicuri, diventa opportuno
+usare la funzione \func{freopen}.
 
 
 \subsection{Le modalità di bufferizzazione}
 \label{sec:file_buffering}
 
-La bufferizzazione è una delle caratteristiche principali della interfaccia
-degli stream; lo scopo è quello di ridurre al minimo il numero di system call
-(\func{read} o \func{write}) eseguite nelle operazioni di input/output. Questa
-funzionalità è assicurata automaticamente dalla libreria, ma costituisce anche
-una degli aspetti più comunemente fraintesi, in particolare per quello che
-riguarda l'aspetto della scrittura dei dati sul file.
-
-I caratteri che vengono scritti su uno stream normalmente vengono accumulati
-in un buffer e poi trasmessi in blocco in maniera asincrona rispetto alla
-scrittura (quello che viene chiamato lo \textsl{scarico}, dall'ingelese 
-\textit{flush}, dei dati) tutte le volte che il buffer viene riempito. Un
-comportamento analogo avviene anche in lettura (cioè dal file viene letto un
-blocco di dati, anche se se ne sono richiesti una quantità inferiore), ma la
-cosa ovviamente ha rilevanza inferiore, dato che i dati letti sono sempre gli
-stessi; in caso di scrittura invece, quando si ha un accesso contemporaneo
-allo stesso file (ad esempio da parte di un altro processo) si potranno vedere
-solo le parti effettivamente scritte, e non quelle ancora presenti nel buffer.
-
-Allo stesso modo, se si sta facendo dell'input/output interattivo bisognerà
-tenere presente le caratteristiche delle operazioni di scarico dei dati,
-poiché non è detto che ad una scrittura sullo stream corrisponda una immediata
-scrittura sul dispositivo.
-
-Per rispondere ad esigenze diverse, lo standard definisce tre distinte modalità
-in cui può essere eseguita la bufferizzazione, delle quali occorre essere ben
-consapevoli, specie in caso di lettura e scrittura da dispositivi interattivi:
+La bufferizzazione è una delle caratteristiche principali della
+interfaccia degli stream; lo scopo è quello di ridurre al minimo il
+numero di system call (\func{read} o \func{write}) eseguite nelle
+operazioni di input/output. Questa funzionalità è assicurata
+automaticamente dalla libreria, ma costituisce anche una degli aspetti
+più comunemente fraintesi, in particolare per quello che riguarda
+l'aspetto della scrittura dei dati sul file.
+
+I caratteri che vengono scritti su uno stream normalmente vengono
+accumulati in un buffer e poi trasmessi in blocco in maniera asincrona
+rispetto alla scrittura (quello che viene chiamato lo \textsl{scarico}
+dei dati, dall'inglese \textit{flush}) tutte le volte che il buffer
+viene riempito. Un comportamento analogo avviene anche in lettura (cioè
+dal file viene letto un blocco di dati, anche se se ne sono richiesti
+una quantità inferiore), ma la cosa ovviamente ha rilevanza inferiore,
+dato che i dati letti sono sempre gli stessi; in caso di scrittura
+invece, quando si ha un accesso contemporaneo allo stesso file (ad
+esempio da parte di un altro processo) si potranno vedere solo le parti
+effettivamente scritte, e non quelle ancora presenti nel buffer.
+
+Allo stesso modo, se si sta facendo dell'input/output interattivo
+bisognerà tenere presente le caratteristiche delle operazioni di scarico
+dei dati, poiché non è detto che ad una scrittura sullo stream
+corrisponda una immediata scrittura sul dispositivo.
+
+Per rispondere ad esigenze diverse, lo standard definisce tre distinte
+modalità in cui può essere eseguita la bufferizzazione, delle quali
+occorre essere ben consapevoli, specie in caso di lettura e scrittura da
+dispositivi interattivi:
 \begin{itemize}
 \item \textit{unbuffered}: in questo caso non c'è bufferizzazione ed i
   caratteri vengono trasmessi direttamente al file non appena possibile
   (effettuando immediatamente una \func{write}).
-\item \textit{line buffered}: in questo caso i caratteri vengono normalmente
-  trasmessi al file in blocco ogni volta che viene incontrato un carattere di
-  \textit{newline} (il carattere ASCII \verb|\n|).
-\item \textit{fully buffered}: in questo caso i caratteri vengono trasmessi da
-  e verso il file in blocchi di dimensione opportuna.
+\item \textit{line buffered}: in questo caso i caratteri vengono
+  normalmente trasmessi al file in blocco ogni volta che viene
+  incontrato un carattere di \textit{newline} (il carattere ASCII
+  \verb|\n|).
+\item \textit{fully buffered}: in questo caso i caratteri vengono
+  trasmessi da e verso il file in blocchi di dimensione opportuna.
 \end{itemize}
 
-Lo standard ANSI C specifica inoltre che lo standard output e lo standard
-input siano aperti in modalità \textit{fully buffered} quando non fanno
-riferimento ad un dispositivo interattivo, e che lo standard error non sia mai
-aperto in modalità \textit{fully buffered}.
+Lo standard ANSI C specifica inoltre che lo standard output e lo
+standard input siano aperti in modalità \textit{fully buffered} quando
+non fanno riferimento ad un dispositivo interattivo, e che lo standard
+error non sia mai aperto in modalità \textit{fully buffered}.
 
 Linux, come BSD e SVr4, specifica il comportamento di default in maniera
 ancora più precisa, e cioè impone che lo standard error sia sempre
@@ -327,7 +333,7 @@ La funzione effettua uno scarico di tutti i dati presenti nei buffer di uscita
 e scarta tutti i dati in ingresso, se era stato allocato un buffer per lo
 stream questo verrà rilasciato. La funzione effettua lo scarico solo per i
 dati presenti nei buffer in user space usati dalle \acr{glibc}; se si essere
-sicuri che il kernel forzi la scrittura su disco occorrà effettuare . 
+sicuri che il kernel forzi la scrittura su disco occorrerà effettuare . 
 
 Linux supporta, come estensione implementata dalle \acr{glibc}, anche una
 altra funzione, \func{fcloseall}, che serve a chiudere tutti i file, il suo
@@ -353,32 +359,36 @@ ricchezza delle funzioni disponibili per le operazioni di lettura e
 scrittura sui file. Sono infatti previste ben tre diverse modalità
 modalità di input/output non formattato:
 \begin{enumerate}
-\item\textsl{a blocchi} in cui legge/scrive un blocco di dati alla
-  volta, vedi \secref{sec:file_block_io}.
+\item\textsl{binario} in cui legge/scrive un blocco di dati alla
+  volta, vedi \secref{sec:file_binary_io}.
 \item\textsl{di linea} in cui si legge/scrive una linea (terminata dal
   carattere di newline \verb|\n|) alla volta, vedi
   \secref{sec:file_line_io}.
-\item\textsl{a catatteri} in cui si legge/scrive un carattere alla
+\item\textsl{a caratteri} in cui si legge/scrive un carattere alla
   volta (con la bufferizzazione gestita automaticamente dalla libreria),
   vedi \secref{sec:file_char_io}.
 \end{enumerate}
-e una modalità di input/output formattato.
-
-A differenza dell'interfaccia dei file descriptor il raggiungimento
-della fine del file è considerato un errore, e viene notificato come
-tale dai valori di uscita delle varie funzioni; nella maggior parte dei
-casi questo avviene con la restituzione del valore intero (di tipo
-\type{int}) \macro{EOF}\footnote{la costante deve essere negativa, le
-  \acr{glibc} usano -1, altre implementazioni possono avere valori
-  diversi.}  definito anch'esso nell'header \func{stdlib.h}.
-
-Dato che le funzioni dell'interfaccia degli stream sono funzioni di
-libreria, esse non settano la variabile \var{errno}, che mantiene il
-valore settato dalla system call che ha riportato l'errore, ma siccome
-la condizione di end-of-file è anch'essa segnalata come errore, non
-esisterebbe alcuna possibilità di distinguerla da un errore (\var{errno}
-potrebbe essere stata settata in una altra occasione, vedi
-\secref{sec:sys_errno}). 
+ed inoltre la modalità di input/output formattato.
+
+A differenza dell'interfaccia dei file descriptor il raggiungimento della fine
+del file è considerato un errore, e viene notificato come tale dai valori di
+uscita delle varie funzioni; nella maggior parte dei casi questo avviene con
+la restituzione del valore intero (di tipo \type{int}) \macro{EOF}\footnote{la
+  costante deve essere negativa, le \acr{glibc} usano -1, altre
+  implementazioni possono avere valori diversi.}  definito anch'esso
+nell'header \func{stdlib.h}.
+
+Dato che le funzioni dell'interfaccia degli stream sono funzioni di libreria
+che si appoggiano a delle system call, esse non settano direttamente la
+variabile \var{errno}, che mantiene il valore settato dalla system call che ha
+riportato l'errore. 
+
+Siccome la condizione di end-of-file è anch'essa segnalata come errore, nasce
+il problema di come distinguerla da un errore effettivo; basarsi solo sul
+valore di ritorno della funzione e controllare il valore di \var{errno}
+infatti non basta, dato che quest'ultimo potrebbe essere stato settato in una
+altra occasione, (si veda \secref{sec:sys_errno} per i dettagli del
+funzionamento di \var{errno}).
 
 Per questo motivo tutte le implementazioni delle librerie standard
 mantengono per ogni stream almeno due flag all'interno dell'oggetto
@@ -397,11 +407,12 @@ questi flag possono essere riletti dalle funzioni:
   flag sono settati. 
 \end{functions}
 \noindent si tenga presente comunque che la lettura di questi flag segnala
-soltanto che si è avuto un errore, o si è raggiunta la fine del file, in
-una precedente operazione sullo stream.
+soltanto che c'è stato un errore, o che si è raggiunta la fine del file in una
+qualunque operazione sullo stream, il controllo quindi deve essere effettuato
+ogni volta che si chiama una funzione di libreria.
 
-Entrambi i flag (di errore e di end-of-file) possono essere cancellati
-usando la funzione \func{clearerr}, il cui prototipo è:
+Entrambi i flag (di errore e di end-of-file) possono essere cancellati usando
+la funzione \func{clearerr}, il cui prototipo è:
 \begin{prototype}{stdio.h}{void clearerr( FILE *stream)}
   Cancella i flag di errore ed end-of-file di \param{stream}. 
 \end{prototype}
@@ -410,14 +421,83 @@ corretta la causa di un errore per evitare di mantenere i flag attivi,
 così da poter rilevare una successiva ulteriore condizione di errore.
 
 
-\subsection{Input/output a blocchi}
-\label{sec:file_block_io}
+\subsection{Input/output binario}
+\label{sec:file_binary_io}
+
+La prima modalità di input/output non formattato ricalca quella della
+intefaccia dei file descriptor, e provvede semplicemente la scrittura e
+la lettura dei dati da un buffer verso un file e vicecersa. In generale
+questa è la modalità che si usa quando si ha a che fare con dati non
+formattati. Le due funzioni che si usano per l'I/O binario sono:
+\begin{functions}
+  \headdecl{stdio.h} 
+  \funcdecl{size\_t fread(void * ptr, size\_t size,
+    size\_t nmemb, FILE * stream)} 
+  \funcdecl{size\_t fwrite(const void *
+    ptr, size\_t size, size\_t nmemb, FILE * stream)}
+  
+  Le funzioni rispettivamente leggono e scrivono \param{nmemb} elementi
+  di dimensione \param{size} dal buffer \param{ptr} al file \param{stream}.
+  
+  Entrambe le funzioni ritornano il numero di elementi letti o scritti,
+  in caso di errore o fine del file viene restituito un numero di
+  elementi inferiore al richiesto.
+\end{functions}
+
+In genere si usano queste funzioni quando si devono trasferire su file
+blocchi di dati binari in maniera compatta e veloce; un primo caso di uso
+tipico è quello in cui si salva un vettore (o un certo numero dei suoi
+elementi) con una chiamata del tipo:
+\begin{lstlisting}[labelstep=0,frame=,indent=1cm]{}
+int WriteVect(FILE * stream, double * vec, size_t nelem) 
+{
+    int size, nread;
+    size = sizeof(*vec);
+    if ( (nread = fwrite(vec, size, nelem, stream)) != nelem) {
+        perror("Write error");
+    }
+    return nread;
+}
+\end{lstlisting}
+in questo caso devono essere specificate le dimensioni di ciascun
+elemento ed il numero di quelli che si vogliono scrivere. Un secondo
+caso è invece quello in cui si vuole trasferire su file una struttura;
+si avrà allora una chiamata tipo:
+\begin{lstlisting}[labelstep=0,frame=,indent=1cm]{}
+struct histogram {
+    int nbins; 
+    double max, min;
+    double * bin;
+} histo; 
+
+int WriteStruct(FILE * stream, struct histogram * histo, size_t nelem) 
+{
+    if ( fwrite(vec, sizeof(*histo), 1, stream) !=1) {
+        perror("Write error");
+    }
+    return nread;
+}
+\end{lstlisting}
+in cui si specifica la dimensione dell'intera struttura ed un solo
+elemento. In realtà quello che conta nel trasferimento dei dati sono le
+dimensioni totali, che sono sempre pari al prodotto \func{size * nelem},
+la sola differenza è che le funzioni non ritornano il numero di bytes
+scritti, ma il numero di elementi. 
+
+La funzione \func{fread} legge sempre un numero intero di elementi, se
+si ha un errore o si incontra la fine del file l'oggetto letto
+parzialmente viene scartato
+
+
+Benché queste funzioni assicurino 
 
 
 \subsection{Input/output a caratteri singoli}
 \label{sec:file_char_io}
 
 
+
+
 \subsection{Input/output di linea}
 \label{sec:file_line_io}
 
@@ -454,13 +534,39 @@ operazioni vengono effettuate un una subroutine, che a questo punto
 necessiterà di informazioni aggiuntive rispetto al semplice puntatore allo
 stream; questo può essere evitato con le due funzioni \func{\_\_freadable} e
 \func{\_\_fwritable} i cui prototipi sono:
-
 \begin{functions}
   \headdecl{stdio\_ext.h}
   \funcdecl{int \_\_freadable (FILE * stream)}
-  \funcdecl{int \_\_fwritable(FILE * stream)}
+  Restituisce un valore diverso da zero se \param{stream} consente la lettura.
+
+  \funcdecl{int \_\_fwritable(FILE * stream)}  
+  Restituisce un valore diverso da zero se \param{stream} consente la
+  scrittura.
 \end{functions}
 
+Altre due funzioni, \func{\_\_freading} e \func{\_\_fwriting} servono ad un
+uso ancora più specialistico, il loro prototipo è: 
+\begin{functions}
+  \headdecl{stdio\_ext.h}
+  \funcdecl{int \_\_freading (FILE * stream)}
+  Restituisce un valore diverso da zero se \param{stream} è aperto in sola
+  lettura o se l'ultima operazione è stata di lettura.
+
+  \funcdecl{int \_\_fwriting(FILE * stream)}  
+  Restituisce un valore diverso da zero se \param{stream} è aperto in sola
+  scrittura o se l'ultima operazione è stata di scrittura.
+\end{functions}
+
+Le due funzioni hanno lo scopo di determinare di che tipo è stata l'ultima
+operazione eseguita su uno stream aperto in lettura/scrittura; ovviamente se
+uno stream è aperto in sola lettura (o sola scrittura) la modalità dell'ultima
+operazione è sempre determinata; l'unica ambiguità è quando non sono state
+ancora eseguite operazioni, in questo caso le funzioni rispondono come se
+una operazione ci fosse comunque stata.
+
+La conoscenza dell'ultima operazione effettuata su uno stream aperto in
+lettura/scrittura è utile in quanto permette di trarre conclusioni sullo stato
+del buffer e del suo contenuto.
 
 
 \subsection{Il controllo della bufferizzazione}