From: Simone Piccardi Date: Mon, 24 Dec 2001 18:16:47 +0000 (+0000) Subject: Completata revisione capitolo 2, aggiunte le variadic function e X-Git-Url: https://gapil.gnulinux.it/gitweb/?p=gapil.git;a=commitdiff_plain;h=8afc898ae5530e3ef1073505c894bb1b738ec916 Completata revisione capitolo 2, aggiunte le variadic function e proseguito sull'output formattato --- diff --git a/filestd.tex b/filestd.tex index a106ff5..2e9b05a 100644 --- a/filestd.tex +++ b/filestd.tex @@ -2,9 +2,9 @@ \label{cha:files_std_interface} Esamineremo in questo capitolo l'interfaccia standard ANSI C per i file, -quella che viene comunemente detta interfaccia degli \textit{stream}. -Dopo una breve sezione introduttiva tratteremo le funzioni base per la -gestione dell'input/output, mentre tratteremo le caratteristiche più avanzate +quella che viene comunemente detta interfaccia degli \textit{stream}. Dopo +una breve sezione introduttiva tratteremo le funzioni base per la gestione +dell'input/output, mentre tratteremo le caratteristiche più avanzate dell'interfaccia nell'ultima sezione. @@ -940,13 +940,14 @@ famiglia \func{printf}; le tre pi \bodydesc{Le funzioni ritornano il numero di caratteri stampati.} \end{functions} \noindent le prime due servono per stampare su file (lo standard output -o quello specificato) la terza permette di stampare su una stringa, in -genere l'uso di \func{sprintf} è sconsigliato in quanto è possibile, se -non si ha la sicurezza assoluta sulle dimensioni del risultato della -stampa, eccedere le dimensioni di \param{str}; per questo motivo si -consiglia l'uso dell'alternativa: +o quello specificato) la terza permette di stampare su una stringa, in genere +l'uso di \func{sprintf} è sconsigliato in quanto è possibile, se non si ha la +sicurezza assoluta sulle dimensioni del risultato della stampa, eccedere le +dimensioni di \param{str} con conseguente sovrascrittura di altre varibili e +possibili buffer overflow; per questo motivo si consiglia l'uso +dell'alternativa: \begin{prototype}{stdio.h} -{snprintf(char *str, size\_t size, const char *format, ...);} +{snprintf(char *str, size\_t size, const char *format, ...)} Identica a \func{sprintf}, ma non scrive su \param{str} più di \param{size} caratteri. \end{prototype} @@ -1027,18 +1028,20 @@ la lunghezza). \textbf{Valore} & \textbf{Significato}\\ \hline \hline - \cmd{\#} & \\ - \cmd{0} & \\ - \cmd{-} & \\ - \cmd{' '} & \\ - \cmd{+} & \\ + \cmd{\#} & Chiede la conversione in forma alternativa. \\ + \cmd{0} & La conversione è riempita con zeri alla sinistra del valore.\\ + \cmd{-} & La conversione viene allineata a sinistra sul bordo del campo.\\ + \cmd{' '}& Mette uno spazio prima di un numero con segno di valore + positivo\\ + \cmd{+} & Mette sempre il segno ($+$ o $-$) prima di un numero.\\ \hline \end{tabular} \caption{I valori dei flag per il formato di \func{printf}} \label{tab:file_format_flag} \end{table} - +Dettagli ulteriori sulle varie opzioni possono essere trovati nella man page +di \func{printf} e nella documentazione delle \acr{glibc}. \begin{table}[htb] \centering @@ -1047,23 +1050,74 @@ la lunghezza). \textbf{Valore} & \textbf{Significato} \\ \hline \hline - \cmd{hh} & \\ - \cmd{h} & \\ - \cmd{l} & \\ - \cmd{ll} & \\ - \cmd{L} & \\ - \cmd{q} & \\ - \cmd{j} & \\ - \cmd{z} & \\ - \cmd{t} & \\ + \cmd{hh} & una conversione intera corriponde a un \type{char} con o senza + segno, o il puntatore per il numero dei parametri \cmd{n} è di + tipo \type{char}.\\ + \cmd{h} & una conversione intera corriponde a uno \type{short} con o + senza segno, o il puntatore per il numero dei parametri \cmd{n} + è di tipo \type{short}.\\ + \cmd{l} & una conversione intera corriponde a un \type{long} con o + senza segno, o il puntatore per il numero dei parametri \cmd{n} + è di tipo \type{long}, o il carattere o la stringa seguenti + sono in formato esteso.\\ + \cmd{ll} & una conversione intera corriponde a un \type{long long} con o + senza segno, o il puntatore per il numero dei parametri \cmd{n} + è di tipo \type{long long}.\\ + \cmd{L} & una conversione in virgola mobile corrisponde a un + \type{double}.\\ + \cmd{q} & sinonimo di \cmd{ll}.\\ + \cmd{j} & una conversione intera corrisponde a un \type{intmax\_t} o + \type{uintmax\_t}.\\ + \cmd{z} & una conversione intera corrisponde a un \type{size\_t} o + \type{ssize\_t}.\\ + \cmd{t} & una conversione intera corrisponde a un \type{ptrdiff\_t}.\\ \hline \end{tabular} \caption{Il modificatore di tipo di dato per il formato di \func{printf}} \label{tab:file_format_type} \end{table} +Una versione alternativa delle funzioni di output formattato, che permettono +di usare il puntatore ad una lista di argomenti (vedi \secref{proc_variadic}), +sono le seguenti: +\begin{functions} + \headdecl{stdio.h} + + \funcdecl{int vprintf(const char *format, va\_list ap)} Stampa su + \var{stdout} gli argomenti della lista \param{ap}, secondo il formato + specificato da \param{format}. + + \funcdecl{int vfprintf(FILE *stream, const char *format, va\_list ap)} + Stampa su \param{stream} gli argomenti della lista \param{ap}, secondo il + formato specificato da \param{format}. + + \funcdecl{int vsprintf(char *str, const char *format, va\_list ap)} Stampa + sulla stringa \param{str} gli argomenti della lista \param{ap}, secondo il + formato specificato da \param{format}. + \bodydesc{Le funzioni ritornano il numero di caratteri stampati.} +\end{functions} +\noindent con queste funzioni diventa possibile selezionare gli argomenti che +si vogliono passare ad una routine di stampa, passando direttamente la lista +tramite il parametro \param{ap}. Per poter far questo ovviamente la lista dei +parametri dovrà essere opportunamente trattata (l'argomento è esaminato in +\secref{sec:proc_variadic}), e dopo l'esecuzione della funzione l'argomento +\param{ap} non sarà più utilizzabile (in generale dovrebbe essere eseguito un +\macro{va\_end(ap)} ma in Linux questo non è necessario). + +Come per \func{sprintf} anche per \func{vsprintf} esiste una analoga +\func{vsnprintf} che pone un limite sul numero di caratteri che vengono +scritti sulla stringa di destinazione: +\begin{prototype}{stdio.h} +{vsnprintf(char *str, size\_t size, const char *format, va\_list ap)} + Identica a \func{vsprintf}, ma non scrive su \param{str} più di + \param{size} caratteri. +\end{prototype} +Per evitare questi problemi con la stampa su stringhe di lunghezza +insuffciente le \acr{glibc} supportano una estensione specifica GNU che alloca +dinamicamente tutto lo spazio necessario; l'estensione si attiva al solito +definendo \macro{\_GNU\_SOURCE}, \subsection{Posizionamento su uno stream} diff --git a/process.tex b/process.tex index 932b80d..f616206 100644 --- a/process.tex +++ b/process.tex @@ -328,41 +328,43 @@ indirizzi virtuali ai quali il processo pu programma C viene suddiviso nei seguenti segmenti: \begin{enumerate} -\item Il segmento di testo (o \textit{text segment}). Contiene il codice - macchina del programma e le costanti statiche. Normalmente viene condiviso, - in modo che più processi (anche diversi nel caso di librerie) possano - utilizzarlo, e viene marcato in sola lettura per evitare sovrascritture +\item Il segmento di testo o \textit{text segment}. Contiene il codice del + programma, delle funzioni di librerie da esso utilizzate, e le costanti. + Normalmente viene condiviso fra tutti i processi che eseguono lo stesso + programma (e anche da processi che eseguono altri programmi nel caso delle + librerie). Viene marcato in sola lettura per evitare sovrascritture accidentali (o maliziose) che ne modifichino le istruzioni. Viene allocato da \func{exec} all'avvio del programma e resta invariato per tutto il tempo dell'esecuzione. -\item Il segmento dei dati (o \textit{data segment}). Contiene le variabili - globali (cioè quelle definite al di fuori di tutte le funzioni). Di norma è - diviso in due parti. +\item Il segmento dei dati o \textit{data segment}. Contiene le variabili + globali (cioè quelle definite al di fuori di tutte le funzioni che + compongono il programma) e le variabili statiche (cioè quelle dichiarate con + l'attributo \type{static}). Di norma è diviso in due parti. La prima parte è il segmento dei dati inizializzati, che contiene le - variabili globali il cui valore è stato assegnato esplicitamente. Ad esempio + variabili il cui valore è stato assegnato esplicitamente. Ad esempio se si definisce: -\begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} + \begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} double pi = 3.14; -\end{lstlisting} + \end{lstlisting} questo valore sarà immagazzinato in questo segmento. La memoria di questo - segmento viene preallocato dalla \func{exec} e inizializzata ai valori + segmento viene preallocata all'avvio del programma e inizializzata ai valori specificati. La seconda parte è il segmento dei dati non inizializzati, che contiene le - variabili globali il cui valore è stato non è assegnato esplicitamente. Ad - esempio se si definisce: -\begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} + variabili il cui valore è stato non è assegnato esplicitamente. Ad esempio + se si definisce: + \begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} int vect[100]; -\end{lstlisting} + \end{lstlisting} questo valore sarà immagazzinato in questo segmento. Anch'esso viene allocato all'avvio, e tutte le variabili vengono inizializzate a zero (ed i puntatori a \macro{NULL}). Storicamente questo segmento viene chiamato BBS (da \textit{block started by - symbol}. La sua dimensione è fissa. + symbol}). La sua dimensione è fissa. \item Lo \textit{heap}. Tecnicamente lo si può considerare l'estensione del segmento dati, a cui di solito è posto giusto di seguito. È qui che avviene @@ -393,37 +395,44 @@ programma C viene suddiviso nei seguenti segmenti: Una disposizione tipica di questi segmenti è riportata in \figref{fig:proc_mem_layout}. Usando il comando \cmd{size} su un programma se ne può stampare le dimensioni dei segmenti di testo e di dati (inizializzati e -BSS); il BSS però non è mai salvato sul file, in quanto viene inizializzato a -zero al caricamento del programma. +BSS); si tenga presente però che il BSS non è mai salvato sul file che +contiene l'eseguibile, dato che viene sempre inizializzato a zero al +caricamento del programma. \subsection{Allocazione della memoria per i programmi C} \label{sec:proc_mem_alloc} -Il C supporta due tipi di allocazione della memoria, l'allocazione statica è -quella in cui vanno le variabili globali e le variabili statiche (e viene -effettuata nel segmento dei dati), lo spazio per queste variabili viene -allocati all'avvio del programma (come parte delle operazioni svolte da -\func{exec}) e non viene liberato fino alla sua conclusione. +Il C supporta, a livello di linguaggio, soltanto due modalità di allocazione +della memoria: l'\textsl{allocazione statica} e l'\textsl{allocazione + automatica}. + +L'\textsl{allocazione statica} è quella con cui sono memorizzate le variabili +globali e le variabili statiche, cioè le variabili il cui valore deve essere +mantenuto per tutta la durata del programma. Come accennato queste variabili +vengono allocate nel segmento dei dati all'avvio del programma (come parte +delle operazioni svolte da \func{exec}) e lo spazio da loro occupato non viene +liberato fino alla sua conclusione. -L'allocazione automatica è quella che avviene per le cosiddette variabili -automatiche, cioè gli argomenti delle funzioni o le variabili locali. Lo -spazio per queste variabili viene allocato nello stack quando viene eseguito -comando di invocazione della funzione e liberato quando si esce dalla -medesima. +L'\textsl{allocazione automatica} è quella che avviene per gli argomenti di +una funzione e per le sue variabili locali (le cosiddette \textsl{variabili + automatiche}), che esistono solo per la durata della funzione. Lo spazio +per queste variabili viene allocato nello stack quando viene eseguita la +funzione e liberato quando si esce dalla medesima. -Esiste però un terzo tipo di allocazione, che non è prevista dal linguaggio C, -che è l'allocazione dinamica della memoria, necessaria quando il quantitativo -di memoria che serve è determinabile solo in corso di esecuzione del -programma. +Esiste però un terzo tipo di allocazione, l'\textsl{allocazione dinamica della + memoria}, che non è prevista direttamente all'interno del linguaggio C, ma +che è necessaria quando il quantitativo di memoria che serve è determinabile +solo durante il corso dell'esecuzione del programma. Il C non consente di usare variabili allocate dinamicamente, non è possibile cioè definire in fase di programmazione una variabile le cui dimensioni -possano essere modificate durante l'esecuzione del programma; però le librerie -del C forniscono una serie opportuna di funzioni per permettere l'allocazione -dinamica di spazio in memoria (in genere nello heap, usando la system call -\func{sbrk}), solo che a questo punto detto spazio sarà accessibile solo in -maniera indiretta attraverso dei puntatori. +possano essere modificate durante l'esecuzione del programma. Per questo le +librerie del C forniscono una serie opportuna di funzioni per eseguire +l'allocazione dinamica di memoria (in genere nello heap). Le variabili il +cui contenuto è allocato in questo modo non potranno essere usate direttamente +come le altre, ma l'accesso sarà possibile solo in maniera indiretta, +attraverso dei puntatori. \subsection{Le funzioni \func{malloc}, \func{calloc}, \func{realloc} e @@ -431,7 +440,8 @@ maniera indiretta attraverso dei puntatori. \label{sec:proc_mem_malloc} Le funzioni previste dallo standard ANSI C per la gestione della memoria sono -quattro, i prototipi sono i seguenti: +quattro: \func{malloc}, \func{calloc}, \func{realloc} e \func{free}, i loro +prototipi sono i seguenti: \begin{functions} \headdecl{stdlib.h} \funcdecl{void *calloc(size\_t size)} @@ -458,60 +468,59 @@ quattro, i prototipi sono i seguenti: La funzione non ritorna nulla e non riporta errori. \end{functions} -Il puntatore che le funzioni di allocazione ritornano è garantito essere -sempre correttamente allineato per tutti i tipi di dati; ad esempio sulle -macchine a 32 bit in genere è allineato a multipli di 4 byte e sulle macchine -a 64 bit a multipli di 8 byte. +Il puntatore ritornato dalle funzioni di allocazione è garantito essere sempre +allineato correttamente per tutti i tipi di dati; ad esempio sulle macchine a +32 bit in genere è allineato a multipli di 4 byte e sulle macchine a 64 bit a +multipli di 8 byte. In genere su usano le funzioni \func{malloc} e \func{calloc} per allocare -dinamicamente la memoria necessaria al programma, siccome i puntatori +dinamicamente la memoria necessaria al programma, e siccome i puntatori ritornati sono di tipo generico non è necessario effettuare un cast per assegnarli a puntatori al tipo di variabile per la quale si effettua la allocazione. La memoria allocata dinamicamente deve essere esplicitamente rilasciata usando \func{free}\footnote{le glibc provvedono anche una funzione \func{cfree} - definita per compatibilità con SunOS, che è deprecata} una volta che non + definita per compatibilità con SunOS, che è deprecata.} una volta che non sia più necessaria. Questa funzione vuole come parametro un puntatore restituito da una precedente chiamata a una qualunque delle funzioni di -allocazione e che non sia già stato liberato da un'altra chiamata a -\func{free}, in caso contrario il comportamento della funzione è indefinito. +allocazione che non sia già stato liberato da un'altra chiamata a \func{free}, +in caso contrario il comportamento della funzione è indefinito. -La funzione \func{realloc} si usa invece per cambiare (in genere aumentare) -la dimensione di un'area di memoria precedentemente allocata, la funzione -vuole in ingresso il puntatore restituito dalla precedente chiamata ad una +La funzione \func{realloc} si usa invece per cambiare (in genere aumentare) la +dimensione di un'area di memoria precedentemente allocata, la funzione vuole +in ingresso il puntatore restituito dalla precedente chiamata ad una \func{malloc} (se è passato un valore \macro{NULL} allora la funzione si comporta come \func{malloc}\footnote{questo è vero per Linux e l'implementazione secondo lo standard ANSI C, ma non è vero per alcune vecchie implementazioni, inoltre alcune versioni delle librerie del C consentivano di usare \func{realloc} anche per un puntatore liberato con - \func{free} purché non ci fossero state altre chiamate a funzioni di - allocazione, questa funzionalità è totalmente deprecata e non è consentita - sotto Linux}), ad esempio quando si deve far crescere la dimensione di un -vettore; in questo caso se è disponibile dello spazio adiacente al precedente -la funzione lo utilizza, altrimenti rialloca altrove un blocco della dimensione -voluta copiandoci automaticamente il contenuto, lo spazio aggiunto non viene -inizializzato. + \func{free} purché non ci fossero state nel frattempo altre chiamate a + funzioni di allocazione, questa funzionalità è totalmente deprecata e non è + consentita sotto Linux.}), ad esempio quando si deve far crescere la +dimensione di un vettore. In questo caso se è disponibile dello spazio +adiacente al precedente la funzione lo utilizza, altrimenti rialloca altrove +un blocco della dimensione voluta, copiandoci automaticamente il contenuto; lo +spazio aggiunto non viene inizializzato. Si deve sempre avere ben presente il fatto che il blocco di memoria restituito da \func{realloc} può non essere una estensione di quello che gli si è passato -come parametro; pertanto esso deve essere trattato allo stesso modo di una -nuova allocazione; in particolare si dovrà \emph{sempre} eseguire la -riassegnazione di \var{ptr} al valore di ritorno della funzione, e -reinizializzare (o provvedere ad un adeguato aggiornamento qualora ancora -servano) tutti gli altri puntatori al blocco di dati ridimensionato. - -Uno degli errori più comuni (specie se si ha a che fare con array di -puntatori) è infatti quello di chiamare \func{free} più di una volta sullo -stesso puntatore; per evitare questo problema una soluzione di ripiego è -quella di assegnare sempre a \macro{NULL} ogni puntatore liberato con -\func{free}, dato che, quando il parametro è un puntatore nullo, -\func{free} non esegue nessuna operazione. - -Linux e le glibc hanno una implementazione delle routine di allocazione che è +in ingresso; per questo si dovrà \emph{sempre} eseguire la riassegnazione di +\var{ptr} al valore di ritorno della funzione, e reinizializzare o provvedere +ad un adeguato aggiornamento di tutti gli altri puntatori all'interno del +blocco di dati ridimensionato. + +Un errore abbastanza frequente (specie se si ha a che fare con array di +puntatori) è quello di chiamare \func{free} più di una volta sullo stesso +puntatore; per evitare questo problema una soluzione di ripiego è quella di +assegnare sempre a \macro{NULL} ogni puntatore liberato con \func{free}, dato +che, quando il parametro è un puntatore nullo, \func{free} non esegue nessuna +operazione. + +Le \acr{glibc} hanno una implementazione delle routine di allocazione che è controllabile dall'utente attraverso alcune variabili di ambiente, in particolare diventa possibile tracciare questo tipo di errori usando la -variabile \macro{MALLOC\_CHECK\_} che quando viene settata mette in uso una +variabile \macro{MALLOC\_CHECK\_} che quando viene definita mette in uso una versione meno efficiente delle funzioni, che però è più tollerante nei confronti di piccoli errori come quello di chiamate doppie a \func{free}; in particolare: @@ -523,33 +532,37 @@ particolare: l'immediata conclusione del programma. \end{itemize*} -Il problema più comune e più difficile da tracciare che si incontra con -l'allocazione della memoria è però quando la memoria non più utilizzata non -viene opportunamente liberata (quello che in inglese viene chiamato -\textit{memory-leak}, traducibile come \textsl{perdita di memoria}). - -Un caso tipico è quando l'allocazione viene fatta da una subroutine per un uso -locale, ma la memoria non viene liberata una volta usata; chiamate ripetute -alla stessa subroutine causeranno a lungo andare un esaurimento della memoria -disponibile, con un conseguente crash dell'applicazione che può avvenire in +Il problema più comune e più difficile da risolvere che si incontra con le +routines di allocazione è quando non viene opportunamente liberata la memoria +non più utilizzata, quello che in inglese viene chiamato \textit{memory-leak}, +(cioè \textsl{perdita di memoria}). + +Un caso tipico che illustra il problema è quello in cui l'allocazione di una +variabile viene fatta da una subroutine per un uso locale, ma la memoria non +viene liberata; la funzione esce e la memoria resta allocata. Chiamate +ripetute alla stessa subroutine continueranno ad allocarne ancora, causando a +lungo andare un esaurimento della memoria disponibile e l'impossibilità di +proseguire il programma. Il problema è che l'esaurimento che può avvenire in qualunque momento, e senza nessuna relazione con la subroutine che contiene -l'errore. +l'errore, per questo motivo è sempre complesso trovare un \textit{memory + leak}. -Per questo motivo l'implementazione delle routine di allocazione delle glibc -mette a disposizione una serie di funzionalità (su cui torneremo in -\secref{sec:xxx_advanced}) che permettono di tracciare le allocazioni e -le disallocazione, e definisce anche una serie di possibili agganci che -permettono di sostituire alle funzioni di libreria una propria versione (che -può essere più o meno specializzata per il debugging). +Per ovviare a questi problemi l'implementazione delle routine di allocazione +delle \acr{glibc} mette a disposizione una serie di funzionalità (su cui +torneremo in \secref{sec:xxx_advanced}) che permettono di tracciare le +allocazioni e le disallocazione, e definisce anche una serie di possibili +\textsl{ganci} che permettono di sostituire alle funzioni di libreria una +propria versione (che può essere più o meno specializzata per il debugging). \subsection{La funzione \func{alloca}} \label{sec:proc_mem_alloca} -Una alternativa possibile all'uso di \func{malloc}, che non soffre del tipo -di problemi di memory leak descritti in precedenza è la funzione -\func{alloca} che invece che allocare la memoria nello heap usa lo il -segmento di stack della funzione corrente. La sintassi è identica: +Una alternativa possibile all'uso di \func{malloc}, che non soffre dei di +problemi di memory leak descritti in precedenza, è la funzione \func{alloca}, +che invece di allocare la memoria nello heap usa il segmento di stack della +funzione corrente. La sintassi è identica a quella di \func{malloc}, il suo +prototipo è: \begin{prototype}{stdlib.h}{void *alloca(size\_t size)} Alloca \var{size} byte nel segmento di stack della funzione chiamante. La memoria non viene inizializzata. @@ -558,43 +571,49 @@ segmento di stack della funzione corrente. La sintassi di successo e \macro{NULL} in caso di fallimento, nel qual caso \var{errno} viene settata a \macro{ENOMEM}. \end{prototype} -\noindent ma in questo caso non è più necessario liberare la memoria in quanto -questa viene rilasciata automaticamente al ritorno della funzione. - -Come è evidente questa funzione ha molti vantaggi, e permette di evitare i -problemi di memory leak non essendo più necessaria la deallocazione esplicita; -una delle ragioni principali per usarla è però che funziona anche quando si -usa \func{longjmp} per uscire con un salto non locale da una funzione (vedi -\secref{sec:proc_longjmp}), - -Un altro vantaggio e che in Linux la funzione è molto veloce e non viene -sprecato spazio, infatti non è necessario gestire un pool di memoria da -riservare e si evitano anche i problemi di frammentazione di quest'ultimo che -comportano inefficienze sia nell'allocazione della memoria che nell'esecuzione -della funzione. - -Gli svantaggi sono che questa funzione non è disponibile su tutti gli unix, -(quando non è possibile aumentare le dimensioni dello stack una volta chiamata -una funzione) e quindi l'uso limita la portabilità dei programmi, inoltre se -si cerca di allocare troppa memoria non si ottiene un messaggio di errore, ma -un segnale di \textit{segment violation} analogo a quello che si avrebbe da -una ricorsione infinita. - -Inoltre non è chiaramente possibile usare questa funzione per allocare memoria -che deve poi essere usata anche al di fuori della funzione in cui questa viene -chiamata, in quanto all'uscita dalla funzione lo spazio allocato diventerebbe -libero, e potrebbe essere sovrascritto all'invocazione di nuove funzioni con -conseguenze imprevedibili. Questo è lo stesso problema potenziale che si può -avere con le variabili automatiche, su cui torneremo in -\secref{sec:proc_auto_var}. +\noindent ma in questo caso non è più necessario liberare la memoria (e quindi +non esiste un analogo della \func{free}) in quanto essa viene rilasciata +automaticamente al ritorno della funzione. + +Come è evidente questa funzione ha molti vantaggi, anzitutto permette di +evitare alla readice i problemi di memory leak, dato che non serve più la +deallocazione esplicita; inoltre la deallocazione automatica funziona anche +quando si usa \func{longjmp} per uscire da una subroutine con un salto non +locale da una funzione (vedi \secref{sec:proc_longjmp}). + +Un altro vantaggio è che in Linux la funzione è molto più veloce di +\func{malloc} e non viene sprecato spazio, infatti non è necessario gestire un +pool di memoria da riservare e si evitano così anche i problemi di +frammentazione di quest'ultimo, che comportano inefficienze sia +nella allocazione della memoria che nella esecuzione della allocazione. + +Gli svantaggi sono che questa funzione non è disponibile su tutti gli Unix, e +non è inserita né nello standard POSIX né in SUSv3 (ma è presente in BSD), il +suo utilizzo quindi limita la portabilità dei programmi. Inoltre la funzione +non può essere usata nella lista degli argomenti di una funzione, perché lo +spazio verrebbe allocato nel mezzo degli stessi. + +% Questo è riportato solo dal manuale delle glibc, nelle man page non c'è +% traccia di tutto ciò +% +%Inoltre se si +%cerca di allocare troppa memoria non si ottiene un messaggio di errore, ma un +%segnale di \textit{segment violation} analogo a quello che si avrebbe da una +%ricorsione infinita. + +Inoltre non è chiaramente possibile usare \func{alloca} per allocare memoria +che deve poi essere usata anche al di fuori della funzione in cui essa viene +chiamata, dato che all'uscita dalla funzione lo spazio allocato diventerebbe +libero, e potrebbe essere sovrascritto all'invocazione di nuove funzioni. +Questo è lo stesso problema che si può avere con le variabili automatiche, su +cui torneremo in \secref{sec:proc_auto_var}. \subsection{Le funzioni \func{brk} e \func{sbrk}} \label{sec:proc_mem_sbrk} L'uso di queste funzioni è necessario solo quando si voglia accedere alle -analoghe system call a cui fanno da interfaccia (ad esempio per implementare -una propria versione di \func{malloc}. Le funzioni sono: +analoghe system call a cui fanno da interfaccia. I loro prototipi sono: \begin{functions} \headdecl{unistd.h} \funcdecl{int brk(void *end\_data\_segment)} @@ -612,12 +631,14 @@ una propria versione di \func{malloc}. Le funzioni sono: allocata in caso di successo e \macro{NULL} in caso di fallimento, nel qual caso \macro{errno} viene settata a \macro{ENOMEM}. \end{functions} +\noindent in genere si usa \func{sbrk} con un valore zero per ottenere +l'attuale posizione della fine del segmento dati. Queste funzioni sono state deliberatamente escluse dallo standard POSIX.1 e -per i programmi normali è opportuno usare le funzioni di allocazione standard -descritte in precedenza, che sono costruite su di esse. In genere si usa -\func{sbrk} con un valore zero per ottenere l'attuale posizione della fine -del segmento dati. +per i programmi normali è sempre opportuno usare le funzioni di allocazione +standard descritte in precedenza, che sono costruite su di esse. L'uso di +queste funzione è ristretto alle specifiche necessità di chi debba +implementare una sua versione delle routine di allocazione. % \subsection{La personalizzazione delle funzioni di allocazione} @@ -629,21 +650,21 @@ del segmento dati. Come spiegato in \secref{sec:proc_mem_gen} il kernel gestisce la memoria in maniera trasparente ai processi, decidendo quando rimuovere pagine dalla -memoria per metterle nello swap sulla base dell'utilizzo corrente da parte dei -vari processi. +memoria per metterle nello swap, sulla base dell'utilizzo corrente da parte +dei vari processi. Nell'uso comune un processo non deve preoccuparsi di tutto ciò, in quanto il meccanismo della paginazione riporta in RAM, ed in maniera trasparente, tutte le pagine che gli occorrono; esistono però esigenze particolari in cui non si -vuole che si attivi il meccanismo dello \textit{swapping}, in generale i -motivi per cui si possono avere queste necessità sono sostanzialmente due: +vuole che questo meccanismo si attivi. In generale i motivi per cui si possono +avere di queste necessità sono due: \begin{itemize} -\item La velocità. Il processo della paginazione è trasparente solo se il - programma in esecuzione se non è sensibile al tempo che occorre a riportare - la pagina in memoria; per questo motivi processi critici che hanno esigenze - di tempo reale o tolleranze critiche nella risposte (ad esempio processi che - trattano campionamenti sonori) possono non essere in grado di sopportare - le variazioni della velocità di accesso dovuta alla paginazione. +\item \textsl{La velocità}. Il processo della paginazione è trasparente solo + se il programma in esecuzione se non è sensibile al tempo che occorre a + riportare la pagina in memoria; per questo motivi processi critici che hanno + esigenze di tempo reale o tolleranze critiche nella risposte (ad esempio + processi che trattano campionamenti sonori) possono non essere in grado di + sopportare le variazioni della velocità di accesso dovuta alla paginazione. In certi casi poi un programmatore può conoscere meglio dell'algoritmo di allocazione delle pagine le esigenze specifiche del suo programma e decidere @@ -651,25 +672,25 @@ motivi per cui si possono avere queste necessit delle prestazioni. In genere queste sono esigenze particolari e richiedono anche un aumento delle priorità in esecuzione (vedi \secref{sec:xxx_xxx}). -\item La sicurezza. Se si tengono password o chiavi in memoria queste possono - essere portate su disco dal meccanismo della paginazione, questo rende più - lungo il periodo di tempo in cui i segreti sono presenti in chiaro e più - complessa la loro cancellazione (ad un processo è possibile cancellare la - memoria su cui scrive le sue variabili, ma non può toccare lo spazio disco - su cui la pagina contenente i segreti può essere stata salvata). Per questo - motivo di solito i programmi di crittografia richiedono il blocco di alcune - pagine di memoria. +\item \textsl{La sicurezza}. Se si hanno password o chiavi segrete in chiaro + in memoria queste possono essere portate su disco dal meccanismo della + paginazione. Questo rende più lungo il periodo di tempo in cui detti segreti + sono presenti in chiaro e più complessa la loro cancellazione (ad un + processo è possibile cancellare la memoria su cui scrive le sue variabili, + ma non può toccare lo spazio disco su cui una pagina di memoria può essere + stata salvata). Per questo motivo di solito i programmi di crittografia + richiedono il blocco di alcune pagine di memoria. \end{itemize} Il meccanismo che previene la paginazione di parte della memoria virtuale di -un processo è chiamato \textit{memory locking} (blocco della memoria), il -blocco è sempre associato alle pagine della memoria virtuale del processo, non -con il segmento reale di RAM su cui essa viene mantenuta. +un processo è chiamato \textit{memory locking} (o \textsl{blocco della + memoria}). Il blocco è sempre associato alle pagine della memoria virtuale +del processo, e non al segmento reale di RAM su cui essa viene mantenuta. La regola è che se un segmento di RAM fa da supporto ad almeno una pagina bloccata allora esso viene escluso dal meccanismo della paginazione. I blocchi non si accumulano, se si blocca due volte la stessa pagina non è necessario -sbloccarla due volte, una pagina o è bloccata o no. +sbloccarla due volte, una pagina o è bloccata oppure no. Il \textit{memory lock} persiste fintanto che il processo che detiene la memoria bloccata non la sblocca. Chiaramente la terminazione del processo @@ -681,10 +702,9 @@ I \textit{memory lock} non sono ereditati dai processi figli\footnote{ma mantenuti sullo stesso segmento di RAM del padre, quindi fintanto che un figlio non scrive su un segmento, può usufruire dei memory lock del padre}. Siccome la presenza di un \textit{memory lock} riduce la memoria disponibile -al sistema con un impatto su tutti gli altri processi, solo l'amministratore ha -la capacità di bloccare una pagina. Ogni processo però può sbloccare le sue -pagine. - +al sistema, con un impatto su tutti gli altri processi, solo l'amministratore +ha la capacità di bloccare una pagina. Ogni processo può però sbloccare le sue +pagine. Il sistema pone dei limiti all'ammontare di memoria di un processo che può essere bloccata e al totale di memoria fisica che può dedicare a questo, lo @@ -693,7 +713,6 @@ standard POSIX.1 richiede che sia definita in \file{unistd.h} la costante \textit{memory locking} e la costante \macro{PAGESIZE} in \file{limits.h} per indicare la dimensione di una pagina in byte. - Le funzioni per bloccare e sbloccare singole sezioni di memoria sono \func{mlock} e \func{munlock}; i loro prototipi sono: \begin{functions} @@ -749,41 +768,63 @@ costanti: \end{basedescript} Con \func{mlockall} si può bloccare tutte le pagine mappate nello spazio di -indirizzi del processo, sia che comprendano il segmento di testi, di dati, lo -stack e lo heap e pure le funzioni di libreria chiamate, i file mappati in +indirizzi del processo, sia che comprendano il segmento di testo, di dati, lo +stack, lo heap e pure le funzioni di libreria chiamate, i file mappati in memoria, i dati del kernel mappati in user space, la memoria condivisa. L'uso dei flag permette di selezionare con maggior finezza le pagine da bloccare, ad esempio limitandosi a tutte le pagine allocate a partire da un certo momento. In ogni caso un processo real-time che deve entrare in una sezione critica -deve provvedere a riservare memoria sufficiente prima dell'ingresso, in genere -questo si fa chiamando una funzione che ha allocato una quantità sufficiente -ampia di variabili automatiche, in modo che esse vengano mappate in RAM dallo -stack e poi ci scrive sopra, per scongiurare in partenza un eventuale page -fault causato dal meccanismo di copy on write. +deve provvedere a riservare memoria sufficiente prima dell'ingresso, per +scongiurare in partenza un eventuale page fault causato dal meccanismo di copy +on write. In genere questo si fa chiamando una funzione che ha allocato una +quantità sufficiente ampia di variabili automatiche, in modo che esse vengano +mappate in RAM dallo stack, e poi ci scrive sopra. + -\section{La gestione di parametri e opzioni} +\section{Parametri, opzioni ed ambiente di un processo} \label{sec:proc_options} -Il passaggio dei parametri e delle variabili di ambiente dalla riga di comando -al singolo programma quando viene lanciato è effettuato attraverso le -variabili \var{argc}, \var{argv} che vengono passate al programma -come argomenti della funzione principale. +Tutti i programmi hanno la possibilità di ricevere parametri e opzioni quando +vengono lanciati. Il passaggio dei parametri è effettuato attraverso gli +argomenti \var{argc} e \var{argv} della funzione \func{main}, che vengono +passati al programma dalla shell (o dal processo che esegue la \func{exec}, +secondo le modalità che vedremo in \secref{sec:proc_exec}) quando questo viene +messo in esecuzione. + +Oltre al passaggio dei parametri, un'altra modalità che permette di passare +delle informazioni che modifichino il comportamento di un programma è quello +dell'uso del cosiddetto \textit{environment} (cioè l'uso delle +\textsl{varibili di ambiente}). In questa sezione esamineremo le funzioni che +permettono di gestire parametri e opzioni, e quelle che consentono di +manipolare ed utilizzare le varibili di ambiente. + \subsection{Il formato dei parametri} \label{sec:proc_par_format} In genere passaggio dei parametri al programma viene effettuato dalla shell, che si incarica di leggere la linea di comando e di effettuarne la scansione (il cosiddetto \textit{parsing}) per individuare le parole che la compongono, -ciascuna delle quali viene considerata un parametro; di default per -individuare le parole viene usato come separatore lo spazio (comportamento -modificabile attraverso il settaggio della variabile di ambiente IFS). +ciascuna delle quali viene considerata un parametro. Di norma per individuare +le parole viene usato come carattere di separazione lo spazio o il tabulatore, +ma il comportamento è modificabile attraverso il settaggio della variabile di +ambiente \cmd{IFS}. + +\begin{figure}[htb] + \centering + \includegraphics[width=11cm]{img/argv_argc} + \caption{Esempio dei valori di \var{argv} e \var{argc} generati nella + scansione di una riga di comando.} + \label{fig:proc_argv_argc} +\end{figure} Nella scansione viene costruito il vettore di puntatori \var{argv} inserendo in successione il puntatore alla stringa costituente l'$n$-simo parametro; la variabile \var{argc} viene inizializzata al numero di parametri trovati, in -questo modo il primo parametro è sempre il nome del programma (vedi \nfig). +questo modo il primo parametro è sempre il nome del programma; un esempio di +questo meccanismo è mostrato in \curfig. + \subsection{La gestione delle opzioni} \label{sec:proc_opt_handling} @@ -791,14 +832,13 @@ questo modo il primo parametro In generale un programma unix riceve da linea di comando sia gli argomenti che le opzioni, queste ultime sono standardizzate per essere riconosciute come tali: un elemento di \var{argv} che inizia con \texttt{-} e che non sia un -singolo \texttt{-} o \texttt{--} viene considerato un'opzione. In in genere -le opzioni sono costituite da una lettera preceduta dal meno e possono avere o -no un parametro associato; un comando tipico può essere cioè qualcosa del -tipo: -\begin{verbatim} -touch -r riferimento.txt -m questofile.txt -\end{verbatim} -ed in questo caso le opzioni sono \texttt{m} ed \texttt{r}. +singolo \texttt{-} o un \texttt{--} viene considerato un'opzione. In genere +le opzioni sono costituite da una lettera singola (preceduta dal \cmd{-}) e +possono avere o no un parametro associato; un comando tipico può essere quello +mostrato in \figref{fig:proc_argv_argc}. In quel caso le opzioni sono \cmd{-r} +ed \cmd{-m} e la prima vuole un parametro mentre la seconda no +(\cmd{questofile.txt} è un argomento del programma, non un parametro di +\cmd{-m}). Per gestire le opzioni all'interno dei argomenti a linea di comando passati in \func{argv} le librerie standard del C forniscono la funzione \func{getopt} @@ -814,19 +854,19 @@ riconoscendo le possibili opzioni segnalate con \var{optstring}. \end{prototype} Questa funzione prende come argomenti le due variabili \var{argc} e \var{argv} -passate a \func{main} (vedi \secref{sec:proc_main}) ed una stringa che indica -quali sono le opzioni valide; la funzione effettua la scansione della lista -degli argomenti ricercando ogni stringa che comincia con \cmd{-} e ritorna ogni -volta che trova una opzione valida. +passate a \func{main} ed una stringa che indica quali sono le opzioni valide; +la funzione effettua la scansione della lista degli argomenti ricercando ogni +stringa che comincia con \cmd{-} e ritorna ogni volta che trova una opzione +valida. La stringa \var{optstring} indica quali sono le opzioni riconosciute ed è costituita da tutti i caratteri usati per identificare le singole opzioni, se l'opzione ha un parametro al carattere deve essere fatto seguire un segno di -due punti \var{':'}, nel caso appena accennato ad esempio la stringa di -opzioni sarebbe \var{"r:m"}. +due punti \var{':'}; nel caso di \figref{fig:proc_argv_argc} ad esempio la +stringa di opzioni avrebbe dovuto contenere \var{"r:m"}. La modalità di uso di \func{getopt} è pertanto quella di chiamare più volte la -funzione all'interno di un ciclo fintanto che essa non ritorna il valore -1 +funzione all'interno di un ciclo, fintanto che essa non ritorna il valore -1 che indica che non ci sono più opzioni. Nel caso si incontri un'opzione non dichiarata in \var{optstring} viene ritornato il carattere \texttt{'?'} mentre se un opzione che lo richiede non è seguita da un parametro viene @@ -871,8 +911,8 @@ elementi di \var{argv} che cominciano con il carattere \texttt{'-'}. \end{figure} Quando la funzione trova un'opzione essa ritorna il valore numerico del -carattere, in questo modo si possono prendere le azioni relative usando uno -\func{switch}; la funzione inizializza inoltre alcune variabili globali: +carattere, in questo modo si possono eseguire azioni specifiche usando uno +\func{switch}; \func{getopt} inoltre inizializza alcune variabili globali: \begin{itemize*} \item \var{char * optarg} contiene il puntatore alla stringa parametro dell'opzione. @@ -893,7 +933,7 @@ stampa di messaggi di errore per opzioni non riconosciute, per poi passare al ciclo per la verifica delle opzioni (\texttt{\small 2-27}); per ciascuna delle opzioni possibili si è poi provveduto ad una opportuna azione, ad esempio per le tre opzioni che prevedono un parametro si è effettuata la decodifica del -medesimo, il cui indirizzo è contenuto nella variabile \var{optarg}, +medesimo (il cui indirizzo è contenuto nella variabile \var{optarg}) avvalorando la relativa variabile (\texttt{\small 12-14}, \texttt{\small 15-17} e \texttt{\small 18-20}). Completato il ciclo troveremo in \var{optind} l'indice in \var{argv[]} del primo degli argomenti a linea di @@ -929,13 +969,13 @@ versione estesa di \func{getopt}. \label{sec:proc_environ} Oltre agli argomenti passati a linea di comando ogni processo riceve dal -sistema un \textsl{ambiente}, nella forma di una lista di variabili -(\textit{environment list}) messa a disposizione dal processo, e costruita +sistema un \textsl{ambiente}, nella forma di una lista di variabili (detta +\textit{environment list}) messa a disposizione dal processo, e costruita nella chiamata alla funzione \func{exec} quando questo viene lanciato. Come per la lista dei parametri anche questa lista è un array di puntatori a -caratteri, ciascuno dei quali punta ad una stringa (terminata da un -\macro{NULL}). A differenza di \var{argv[]} però in questo caso non si ha una +caratteri, ciascuno dei quali punta ad una stringa, terminata da un +\macro{NULL}. A differenza di \var{argv[]} in questo caso non si ha una lunghezza dell'array data da un equivalente di \var{argc}, ma la lista è terminata da un puntatore nullo. @@ -967,11 +1007,12 @@ dover ricorrere all'uso di opzioni a linea di comando o di file di configurazione. La shell ad esempio ne usa molte per il suo funzionamento (come \var{PATH} per -la ricerca dei comandi), e alcune di esse (come \var{HOME}, \var{USER}, etc.) -sono definite al login. In genere è cura dell'amministratore definire le -opportune variabili di ambiente in uno script di avvio. Alcune servono poi -come riferimento generico per molti programmi (come \var{EDITOR} che indica -l'editor preferito da invocare in caso di necessità). +la ricerca dei comandi, o \cmd{IFS} per la scansione degli argomenti), e +alcune di esse (come \var{HOME}, \var{USER}, etc.) sono definite al login. In +genere è cura dell'amministratore definire le opportune variabili di ambiente +in uno script di avvio. Alcune servono poi come riferimento generico per molti +programmi (come \var{EDITOR} che indica l'editor preferito da invocare in caso +di necessità). Gli standard POSIX e XPG3 definiscono alcune di queste variabili (le più comuni), come riportato in \ntab. GNU/Linux le supporta tutte e ne definisce @@ -1005,22 +1046,24 @@ anche altre: per una lista pi \label{tab:proc_env_var} \end{table} -Lo standard ANSI C, pur non entrando nelle specifiche di come sono strutturati -i contenuti, definisce la funzione \func{getenv} che permette di ottenere i -valori delle variabili di ambiente, il suo prototipo è: +Lo standard ANSI C prevede l'esistenza di un ambiente, pur non entrando nelle +specifiche di come sono strutturati i contenuti, e definisce la funzione +\func{getenv} che permette di ottenere i valori delle variabili di ambiente, +il cui prototipo è: \begin{prototype}{stdlib.h}{char *getenv(const char *name)} Esamina l'ambiente del processo cercando una stringa che corrisponda a quella specificata da \param{name}. \bodydesc{La funzione ritorna \macro{NULL} se non trova nulla, o il puntatore alla stringa che corrisponde (di solito nella forma - \texttt{NOME=valore}).} + \cmd{NOME=valore}).} \end{prototype} Oltre a questa funzione di lettura, che è l'unica definita dallo standard ANSI -C, in seguito sono state proposte altre da utilizzare per settare e per -cancellare le variabili di ambiente presenti; uno schema delle funzioni -previste nei vari standard unix e disponibili in Linux è riportato in \ntab. +C, nell'evoluzione dei sistemi Unix ne sono state proposte altre, da +utilizzare per settare e per cancellare le variabili di ambiente. Uno schema +delle funzioni previste nei vari standard e disponibili in Linux è riportato +in \ntab. \begin{table}[htb] \centering @@ -1046,43 +1089,43 @@ previste nei vari standard unix e disponibili in Linux \label{tab:proc_env_func} \end{table} -In Linux solo le prime quattro funzioni di \curtab\ sono definite; delle tre -restanti le prime due, \func{putenv} e \func{setenv} servono per assegnare -nuove variabili di ambiente, i loro prototipi sono i seguenti: +In Linux solo le prime quattro funzioni di \curtab\ sono definite, +\func{getenv} l'abbiamo già esaminata; delle tre restanti le prime due, +\func{putenv} e \func{setenv}, servono per assegnare nuove variabili di +ambiente, i loro prototipi sono i seguenti: \begin{functions} \headdecl{stdlib.h} - \funcdecl{int putenv(char *string)} Aggiunge la stringa \param{string} - all'ambiente. - \funcdecl{int setenv(const char *name, const char *value, int overwrite)} Setta la variabile di ambiente \param{name} al valore \param{value}. + \funcdecl{int putenv(char *string)} Aggiunge la stringa \param{string} + all'ambiente. + \bodydesc{Entrambe le funzioni ritornano 0 in caso di successo e -1 per un errore, che è sempre \macro{ENOMEM}.} \end{functions} -\noindent la terza è: +\noindent la terza, \func{unsetenv}, serve a cancellare una variabile di +ambiente; il suo prototipo è: \begin{functions} \headdecl{stdlib.h} - \funcdecl{void unsetenv(const char *name)} - Rimuove la variabile di ambiente \param{name}. + \funcdecl{void unsetenv(const char *name)} Rimuove la variabile di ambiente + \param{name}. \end{functions} +\noindent questa funzione elimina ogni occorrenza della variabile specificata; +se essa non esiste non succede nulla. Non è prevista (dato che la funzione è +\type{void}) nessuna segnalazione di errore. -Per cancellare una variabile di ambiente si usa \func{unsetenv}, che elimina -ogni occorrenza della variabile, se la variabile specificata non esiste non -succede nulla, e non è previsto (dato che la funzione è \type{void}) nessuna -segnalazione di errore. - -Per modificare o aggiungere una variabile di ambiente si possono usare le -funzioni \func{setenv} e \func{putenv}. La prima permette di specificare +Per modificare o aggiungere una variabile di ambiente si possono usare sia +\func{setenv} che \func{putenv}. La prima permette di specificare separatamente nome e valore della variabile di ambiente, inoltre il valore di \param{overwrite} specifica il comportamento della funzione nel caso la variabile esista già, sovrascrivendola se diverso da zero, lasciandola immutata se uguale a zero. La seconda funzione prende come parametro una stringa analoga quella -restituita da \func{getenv}, e sempre nella forma \texttt{NOME=valore}. Se la +restituita da \func{getenv}, e sempre nella forma \var{NOME=valore}. Se la variabile specificata non esiste la stringa sarà aggiunta all'ambiente, se invece esiste il suo valore sarà settato a quello specificato da \func{string}. Si tenga presente che, seguendo lo standard SUSv2, le @@ -1090,12 +1133,12 @@ invece esiste il suo valore sar comportamento è lo stesso delle vecchie \acr{libc4} e \acr{libc5}; nelle \acr{glibc}, dalla versione 2.0 alla 2.1.1, veniva invece fatta una copia, seguendo il comportamento di BSD4.4; dato che questo può dar luogo a perdite - di memoria e non rispetta lo standard il comportamento è stato modificato a + di memoria e non rispetta lo standard. Il comportamento è stato modificato a partire dalle 2.1.2, eliminando anche, sempre in conformità a SUSv2, l'attributo \type{const} dal prototipo.} \func{string} alla lista delle variabili di ambiente; pertanto ogni cambiamento alla stringa in questione si -riflette automaticamente sull'ambiente, e quindi si deve evitare di passare -alla funzione variabili automatiche (per evitare i problemi esposti in +riflette automaticamente sull'ambiente, e quindi si deve evitare di passare a +questa funzione una variabile automatica (per evitare i problemi esposti in \secref{sec:proc_auto_var}). Si tenga infine presente che se si passa a \func{putenv} solo il nome di una @@ -1103,10 +1146,10 @@ variabile (cio \var{=}) allora questa viene cancellata dall'ambiente. Infine se la chiamata di \func{putenv} comporta la necessità di allocare una nuova versione del vettore \var{environ} questo sarà allocato, ma la versione corrente sarà -deallocata solo se anch'essa risultante da una allocazione fatta in precedenza -da un'altra \func{putenv}, il vettore originale (in genere piazzato al di -sopra dello stack, vedi \figref{fig:proc_mem_layout}), o la memoria associata -alle variabili di ambiente eliminate non viene comunque liberata. +deallocata solo se anch'essa è risultante da una allocazione fatta in +precedenza da un'altra \func{putenv}, il vettore originale (in genere piazzato +al di sopra dello stack, vedi \figref{fig:proc_mem_layout}), o la memoria +associata alle variabili di ambiente eliminate non viene comunque liberata. \section{Problematiche di programmazione generica} @@ -1155,9 +1198,9 @@ nella programmazione normale. Talvolta però è necessario che la funzione possa restituire indietro alla funzione chiamante un valore relativo ad uno dei suoi parametri. Per far questo si usa il cosiddetto \textit{value result argument}, si passa cioè, -invece di una normale variabile un puntatore; vedremo alcuni esempi di questa -modalità nelle funzioni che gestiscono i socket (in -\secref{sec:TCPel_functions}) in cui, per permettere al kernel di restituire +invece di una normale variabile, un puntatore alla stessa; vedremo alcuni +esempi di questa modalità nelle funzioni che gestiscono i socket (in +\secref{sec:TCPel_functions}), in cui, per permettere al kernel di restituire informazioni sulle dimensioni delle strutture degli indirizzi utilizzate, viene usato questo meccanismo. @@ -1165,13 +1208,158 @@ viene usato questo meccanismo. \subsection{Il passaggio di un numero variabile di argomenti} \label{sec:proc_variadic} -Come vedremo nei capitoli successivi, non sempre è possibile specificare -un numero fisso di parametri per una funzione. Lo standard ISO C -prevede la possibilità di definire delle \textit{varadic function} che -abbiano un numero variabile di argomenti, ma non provvede nessun -meccanismo con cui queste funzioni possono accedere a questi argomenti. +Come vedremo nei capitoli successivi, non sempre è possibile specificare un +numero fisso di parametri per una funzione. Lo standard ISO C prevede nella +sua sintassi la possibilità di definire delle \textit{variadic function} che +abbiano un numero variabile di argomenti, attraverso l'uso della +\textit{ellipsis} \var{...} nella dichiarazione della funzione; ma non +provvede a livello di linguaggio alcun meccanismo con cui dette funzioni +possono accedere ai loro argomenti. + +L'accesso viene invece realizzato dalle librerie standard che provvedono gli +strumenti adeguati. L'uso delle \textit{variadic function} prevede tre punti: +\begin{itemize*} +\item \textsl{Dichiarare} la funzione come \textit{variadic} usando un + prototipo che contenga una \textit{ellipsis}. +\item \textsl{Definire} la funzione come \textit{variadic} usando lo stesso + \textit{ellipsis}, ed utilizzare le apposite macro che consentono la + gestione di un numero variabile di argomenti. +\item \textsl{Chiamare} la funzione specificando prima gli argomenti fissi, e + a seguire gli addizionali. +\end{itemize*} + +Lo standard ISO C prevede che una \textit{variadic function} abbia sempre +almeno un argomento fisso; prima di effettuare la dichiarazione deve essere +incluso l'apposito header file \file{stdarg.h}; un esempio di dichiarazione è +il prototipo della funzione \func{execl} che vedremo in +\secref{sec:proc_exec}: +\begin{lstlisting}[labelstep=0,frame=,indent=1cm]{} + int execl(const char *path, const char *arg, ...); +\end{lstlisting} +in questo caso la funzione prende due parametri fissi ed un numero variabile +di altri parametri (che verranno a costituire gli elementi successivi al primo +del vettore \var{argv} passato al nuovo processo). Lo standard ISO C richiede +inoltre che l'ultimo degli argomenti fissi sia di tipo +\textit{self-promoting}\footnote{il linguaggio C prevede che quando si + mescolano vari tipi di dati, alcuni di essi possano essere \textsl{promossi} + per compatibilità; ad esempio i tipi \type{float} vengono convertiti + automaticamente a \type{double} ed i \type{char} e gli \type{short} ad + \type{int}. Un tipo \textit{self-promoting} è un tipo che verrebbe promosso + a se stesso.} il che esclude array, puntatori a funzioni e interi di tipo +\type{char} o \type{short} (con segno o meno). Una ulteriore restrizione di +alcuni compilatori è di non dichiarare l'ultimo parametro fisso come +\type{register}. + +Una volta dichiarata la funzione il secondo passo è accedere ai vari parametri +quando la si va a definire. I parametri fissi infatti hanno un loro nome, ma +quelli variabili vengono indicati in maniera generica dalla ellipsis. + +L'unica modalità in cui essi possono essere recuperati è pertanto quella +sequenziale; essi verranno estratti dallo stack secondo l'ordine in cui sono +stati scritti. Per fare questo in \file{stdarg.h} sono definite delle apposite +macro; la procedura da seguire è la seguente: +\begin{enumerate*} +\item Inizializzare un puntatore alla lista degli argomenti di tipo + \type{va\_list} attraverso la macro \macro{va\_start}. +\item Accedere ai vari argomenti opzionali con chiamate successive alla macro + \macro{va\_arg}, la prima chiamata restituirà il primo argomento, la seconda + il secondo e così via. +\item Dichiarare la conclusione dell'estrazione dei parametri invocando la + macro \macro{va\_end}. +\end{enumerate*} +in generale è perfettamente legittimo richiedere meno argomenti di quelli che +potrebbero essere stati effettivamente forniti, e nella esecuzione delle +\macro{va\_arg} ci si può fermare in qualunque momento ed i restanti argomenti +saranno ignorati; se invece si richiedono più argomenti di quelli forniti si +otterranno dei valori indefiniti. Nel caso del \cmd{gcc} poi l'uso della macro +\macro{va\_end} è inutile, ma si consiglia di usarlo ugualmente per +compatibilità. + +Le definizioni delle tre macro sono le seguenti: +\begin{functions} + \headdecl{stdarg.h} + + \funcdecl{void va\_start(va\_list ap, last)} Inizializza il puntatore alla + lista di argomenti \param{ap}; il parametro \param{last} \emph{deve} essere + l'ultimo dei parametri fissi. + + \funcdecl{type va\_arg(va\_list ap, type)} Restituisce il valore del + successivo parametro opzionale, modificando opportunamente \param{ap}; la + macro richiede che si specifichi il tipo dell'argomento attraverso il + parametro \param{type} che deve essere il nome del tipo dell'argomento in + questione. Il tipo deve essere \textit{self-promoting}. + + \funcdecl{void va\_end(va\_list ap)} Conclude l'uso di \param{ap}. +\end{functions} + +In generale si possono avere più puntatori alla lista degli argomenti, +ciascuno andrà inizializzato con \macro{va\_start} e letto con \macro{va\_arg} +e ciascuno potrà scandire la lista degli argomenti per conto suo. + +Dopo l'uso di \macro{va\_end} la variabile \var{ap} diventa indefinita e +successive chiamate a \macro{va\_arg} non funzioneranno. Si avranno risultati +indefiniti anche chiamando \macro{va\_arg} specificando un tipo che non +corrisponde a quello del parametro. + +Un altro limite delle macro è che i passi 1) e 3) devono essere eseguiti nel +corpo principale della funzione, il passo 2) invece può essere eseguito anche +in una subroutine passandole il puntatore alla lista di argomenti; in questo +caso però si richiede che al ritorno della funzione il puntatore non venga più +usato (lo standard richiederebbe la chiamata esplicita di \macro{va\_end}), +dato che il valore di \var{ap} risulterebbe indefinito. + +Esistono dei casi in cui è necessario eseguire più volte la scansione dei +parametri e poter memorizzare una posizione durante la stessa. La cosa più +naturale in questo caso sembrerebbe quella di copiarsi il puntatore alla lista +degli argomenti con una semplice assegnazione. Dato che una delle +realizzazioni più comuni di \macro{va\_list} è quella di un puntatore nello +stack all'indirizzo dove sono stati salvati i parametri, è assolutamente +normale pensare di poter effettuare questa operazione. + +In generale però possono esistere anche realizzazioni diverse, per questo +motivo \macro{va\_list} è definito come tipo opaco e non può essere assegnato +direttamente ad un altra variabile dello stesso tipo. Per risolvere questo +problema lo standard ISO C99\footnote{alcuni sistemi che non hanno questa + macro provvedono al suo posto \macro{\_\_va\_copy} che era il nome proposto + in una bozza dello standard} ha previsto una ulteriore macro che permette di +eseguire la copia di un puntatore alla lista degli argomenti: +\begin{prototype}{stdarg.h}{void va\_copy(va\_list dest, va\_list src)} + Copia l'attuale valore \param{src} del puntatore alla lista degli argomenti + su \param{desc}. +\end{prototype} +\noindent anche in questo caso è buona norma chiudere ogni esecuzione di una +\macro{va\_copy} con una corrispondente \macro{va\_end} sul nuovo puntatore +alla lista degli argomenti. + +La chiamata di una funzione con un numero variabile di argomenti, posto che la +si sia dichiarata e definita come tale, non prevede nulla di particolare; +l'invocazione è identica alle altre, con i parametri, sia quelli fissi che +quelli opzionali, separati da virgole. Quello che però è necessario tenere +presente è come verranno convertiti gli argomenti variabili. + +In Linux gli argomenti dello stesso tipo sono passati allo stesso modo, sia +che siano fissi sia che siano opzionali (alcuni sistemi trattano diversamente +gli opzionali), ma dato che il prototipo non può specificare il tipo degli +argomenti opzionali, questi verranno sempre promossi, pertanto nella ricezione +dei medesimi ocoorrerà tenerne conto (ad esempio un \type{char} verrà visto da +\macro{va\_arg} come \type{int}). + + +Uno dei problemi che si devono affrontare con le funzioni con un numero +variabile di argomenti è che non esiste un modo generico che permetta di +stabilire quanti sono i parametri passati effettivamente in una chiamata. + +Esistono varie modalità per affrontare questo problema; una delle più +immediate è quella di specificare il numero degli argomenti opzionali come uno +degli argomenti fissi. Una variazione di questo metodo è l'uso di un parametro +per specificare anche il tipo degli argomenti (come fa la stringa di formato +per \func{printf}). + +Una modalità diversa, che può essere applicata solo quando il tipo dei +parametri lo rende possibile, è quella che prevede di usare un valore speciale +come ultimo argomento (come fa ad esempio \func{execl} che usa un puntatore +\macro{NULL} per indicare la fine della lista degli argomenti). -(NdT il resto è da fare). \subsection{Potenziali problemi con le variabili automatiche} \label{sec:proc_auto_var} @@ -1180,15 +1368,15 @@ Uno dei possibili problemi che si possono avere con le subroutine restituire alla funzione chiamante dei dati che sono contenuti in una variabile automatica. Ovviamente quando la subroutine ritorna la sezione dello stack che conteneva la variabile automatica potrà essere riutilizzata da -una nuova funzione, con le conseguenze immaginabili di sovrapposizione. +una nuova funzione, con le immaginabili conseguenze di sovrapposizione e +sovrascrittura dei dati. Per questo una delle regole fondamentali della programmazione in C è che -all'uscita di una funzione non deve restare nessun riferimento a variabili -locali di quella funzione; qualora necessiti di utilizzare variabili che -possano essere viste anche dalla funzione chiamante queste devono essere -allocate esplicitamente, o in maniera statica (usando variabili di tipo -\type{static} o \type{extern}), o dinamicamente con una delle funzioni della -famiglia \func{malloc}. +all'uscita di una funzione non deve restare nessun riferimento alle variabili +locali; qualora sia necessirio utilizzare variabili che possano essere viste +anche dalla funzione chiamante queste devono essere allocate esplicitamente, o +in maniera statica (usando variabili di tipo \type{static} o \type{extern}), o +dinamicamente con una delle funzioni della famiglia \func{malloc}. \subsection{Il controllo di flusso non locale} \label{sec:proc_longjmp} diff --git a/sources/Makefile b/sources/Makefile index dd60513..b13e669 100644 --- a/sources/Makefile +++ b/sources/Makefile @@ -8,7 +8,11 @@ CFLADJ=-c OBJ = SockRead.o SockWrite.o -all: forktest errcode echo echod daytimed iterdaytimed daytime +FINAL = forktest errcode echo echod daytimed iterdaytimed daytime testfopen \ + testren + + +all: $(FINAL) testfopen: test_fopen.c $(CC) $(CFLAGS) $^ -o $@ @@ -40,13 +44,22 @@ daytime: SimpleDaytimeTCPClient.c $(OBJ): wrappers.h +# Macro per la generazione della tarball dei sorgenti +package: clean gapil_source.tgz + + +gapil_source.tgz: + tar --exclude=CVS -cvz . -f $@ + mv $@ .. + .PHONY : clean clean: - rm -f daytime iterdaytimed daytimed echod echo errcode forktest + rm -f $(FINAL) rm -f *~ rm -f *.o - + rm -f prova* + rm -f output*