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,
 \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}
 
 \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}
 
 \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}
 
 \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}
 \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}
 
 
 
 \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}).
 \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}
 
 \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
 
 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
 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
 
 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}
 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{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}
   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
 
 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
   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}
 \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.
 
 
 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 a caratteri singoli}
 \label{sec:file_char_io}
 
 
+
+
 \subsection{Input/output di linea}
 \label{sec:file_line_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:
 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)}
 \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}
 
 \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}
 
 
 \subsection{Il controllo della bufferizzazione}