Input ed Output

31.1 Generalita'

Il linguaggio C non prevede dei costrutti per il trattamento delle operazioni di Input/Output. Per gestire tali operazioni sono state via via sviluppate nuove funzioni che appartengono ora alla libreria standard del C. La libreria standard del C che contiene le funzioni che gestiscono le operazioni di I/O si chiama 'stdio'. Le caratteristiche di tali funzioni sono percio' specificate nel file header stdio.h. Le funzioni che trattano i file testi, considerano tali file come flussi di caratteri suddivisi in righe, dove ogni riga termina con un carattere di new line. Se il sistema sul quale si sta lavorando non utilizza tale criterio, la funzione di libreria fara' tutto cio' che e' necessario affinche' i programmi non si accorgano della differenza. Ad esempio, mentre nel mondo Ms-Dos (e quindi Windows) ogni riga di un file di testo termina con una coppia di caratteri, LF/CR (Line Feed e Carriage Return - salto riga e ritorno carrello - cioe' i caratteri di controllo che permettono di spostare il cursore rispettivamente alla riga successiva, cioe' line feed e all'inizio dello schermo, cioe' carriage return), nel mondi Unix/Linux tali righe termina con il solo carattere LF. Le funzioni di libreria che operano in ambiente Ms-Dos effettuano le opportune conversioni, trasformando la coppia LF/CR nel carattere LF. Le funzioni della libreria standard del C effettuano tali conversioni affinche' i programmi che le utilizzano siano portabili, cioe' funzionanti su qualsiasi sistema operativo. Le funzioni classiche di lettura e scrittura su file sono getchar() e putchar(). Un'altra funzione di libreria standard molto usata e' printf(). Una funzione simile a printf() e' sprintf(). La funzione sprintf() si comporta esattamente come la printf() con la differenza che l'output non viene prodotto nello standard output (a video) ma all'interno della stringa specificata:

int sprintf (char *string, char *format, arg1, arg2, etc...)
             ------------ 

	simile a printf():

int printf (char *format, arg1, arg2, etc...)

la differenza tra printf() e sprintf() e' l'argomento char *string che e' una stringa, cioe' un puntatore a char. Cio' significa che prima di specificare gli argomenti che descrivono il formato delle variabili da stampare, e' possibile specificare la stringa all'interno della quale verra' scritto l'output. L'argomento char *format e' un puntatore a char, cioe' una stringa che specifica il formato degli altri argomenti. Ad esempio nella la funzione: printf ("%d", 5), la stringa "%d" specifica che l'argomento successivo deve essere trattato come un intero. Altra funzione analoga a printf() e' scanf(). La funzione scanf() opera allo stesso modo di printf() con la differenza che non serve per stampare dell'output ma per ricevere dell'input. Infatti la funzione scanf() riceve in input dei dati che memorizza all'interno delle variabili specificate utilizzando il formato specificato.

int scanf (char *format, arg1, arg2, etc...)

nella funzione scanf() gli argomenti arg1, arg2 etc, devono essere dei puntatori che puntano alle variabili che dovranno memorizzare i dati letti in input. Un'altra funzione derivata dalla scanf() ed analoga alla sprintf() e' la funzione sscanf(). La funzione sscanf() riceve in input dei dati e li memorizza all'interno di una stringa (similmente a sprintf()). La sintassi e' la seguente:

int sscanf (char *string, char *format, arg1, arg2, etc...)
            ------------  

come si puo' osservare gli argomenti che tale funzione accetta in input sono gli stessi della funzione sprintf(). Anche in questa funzione arg1, arg2 etc, devono essere dei puntatori.

31.2 Accesso ai file

Come visto precedentemente ad ogni programma in esecuzione sono associati 3 canali: lo standard input, lo standard output e lo standard error. Le funzioni viste fin qui leggono dallo standard input e scrivono nello standard output. In C esiste pero' la possibilita' di gestire i file che non sono connessi al programma (come i canali standard) utilizzando una modalita' molto semplice. Qualsiasi file, prima di poter essere letto o scritto, deve essere aperto. Per aprire un file in lettura od in scrittura viene usata la funzione di libreria fopen(). La funzione fopen() accetta come argomenti due puntatori a char, cioe' due stringhe che specificano il nome del file da aprire e la modalita' con la quale deve essere aperto. La sintassi di fopen() e' la seguente:

FILE * fopen (char *nomefile, char *modo);

La funzione fopen() ritorna in output un puntatore a FILE. FILE e' una struttura che contiene alcune informazioni sul file comead esempio l'indirizzo di un buffer, la posizione corrente all'interno del buffer, la modalita' di apertura del file, eventuali errori riscontrati ed altro. Il puntatore alla struttura FILE e' necessario in quanto verra' utilizzato nelle successive operazioni di lettura o scrittura del file. Occorre osservare che FILE e' un nome di un nuovo tipo (come char, int etc) creato utilizzando l'istruzione typedef e non il nome di una struttura. Nel file header stdio.h sara' presente pertanto una dichiarazione di questo tipo:

typedef struct qualcosa
{
	var1;
	var2;
	....
} FILE;

		e non:

struct FILE
{
	var1;
	var2;
	....
};

Per accedere ad un file quindi, sia esso gia' esistente o da creare, occorre dichiarare un puntatore a FILE prima di tutto, chiamare la funzione fopen(), elaborare il file ed infine chiuderlo utilizzando la funzione fclose(). Per aprire un file occorrono percio' le istruzioni seguenti:

FILE * puntatore;

puntatore = fopen("nomefile", "modo");

la funzione fopen() apre il file e ritorna il puntatore collegato al file: tale puntatore viene assegnato alla variabile puntatore 'puntatore' che sara' utilizzata successivamente per leggere o scrivere il file. Il primo argomento di fopen() e' il nome del file, mentre il secondo e' la modalita' con la quale deve essere aperto. Le modalita' di apertura di un file sono: "r" (Read, cioe' lettura), "w" (Write, cioe' scrittura) e "a" (Append, cioe' accodamento). In alcuni sistemi viene fatta una differenza tra file di testo e file binari: per trattare quest'ultimi occorre aggiungere il suffisso 'b' alla stringa che specifica la modalita' (es.: "rb"). Un file aperto in scrittura o in accodamento, se non esiste viene creato. L'operazione di lettura di un file che non esiste o di scrittura su un file sul quale non si hanno i permessi di accesso, produce un errore. In caso di errore fopen() non puo' ritornare un puntatore ad un file percio' ritorna NULL. Una volta aperto un file, e' possibile leggerlo e scriverlo utilizzando le funzioni getc() e putc() oppure fgetc() e fputc(). Entrambe le funzioni accettano in input gli stessi argomenti e si comportano allo stesso modo: la sola differenza e' che generalmente getc e putc sono implementate a livello di macro, percio' non esiste cio' che viene definito overhead, cioe' una sorta di sovraccarico durante l'elaborazione. Infatti e' piu' efficiente chiamare una macro ad ogni lettura di un carattere che non una funzione, in quanto quest'ultima richiede un surplus di elaborazione. La macro viene esplosa in fase di precompilazione, mentre la funzione deve essere richiamata in memoria e gestita (allocazione di un nuovo stack frame, caricamento in memoria della funzione, gestione degli indirizzi, deallocazione dello stack, etc). In ambiente Unix/Linux in realta' esistono altre funzioni di sistema per l'accesso ai file (come open(), close(), read(), write(), etc) ma al momento l'attenzione e' focalizzata alle funzioni della libreria standard del C: tale funzioni sono utilizzabili sempre, indipendentemente dal sistema operativo utilizzato. Tornando alle funzioni getc() e putc(), si puo' affermare che tali funzioni sono una generalizzazione delle funzioni getchar() e putchar() viste in precedenza. Infatti le funzioni getc() e putc() accettano un qualsiasi file (o meglio, puntatore a FILE) come argomento, mentre le funzioni getchar() e putchar() sono collegate unicamente ai file speciali: standard input e standard output. In altre parole getchar() corrisponde a getc(stdin) e putchar() a putc((c), stdout). Tutte le funzioni appena viste permettono la lettura o la scrittura di un carattere per volta, ma la libreria standard prevede anche delle funzioni per il trattamento dell'input e dell'output formattato. Le funzioni fprintf() ed fscanf() sono analoghe alle funzioni printf() e scanf(), ma l'output viene prodotto su file e l'input viene letto da file. Una volta ottenuto il puntatore dalla funzione fopen() e' possibile utilizzarlo per le operazioni di lettura o scrittura:

int c;

FILE * pfile;

pfile = fopen("nomefile", "modo");

c = getc(pfile);

       oppure:

c = 'a';

putc (c, pfile);

Una volta finito di elaborare il file, occorre chiamare la funzione fclose(). Tale funzione opera in modo inverso alla funzione fopen(): mentre quest'ultima effettua il collegamento tra il file ed il programma, la funzione fclose() interrompe tale collegamento. Dopo fclose() il puntatore a FILe puo' essere utilizzato per accedere ad altri file. Su alcuni sistemi operativi esiste un limite sul numero massimo di file che e' possibile tenere aperti contemporaneamente, e' buona norma quindi, chiudere un file non appena si e' finito di utilizzarlo ed il suo puntatore e' diventato inutile. Infine, putc() scrive su un buffer (area di memoria ram): solo con la chiamata fclose() il contenuto del buffer viene effettivamente scritto sul file. Quando un programma termina senza errori, fclose() viene chiamata automaticamente per chiudere tutti i file aperti. Ogni volta che viene eseguito un programma C, il sistema si occupa di collegare a tale programma 3 file di default: lo standard input, lo standard output e lo standard error. Piu' esattamente, quando viene eseguito un programma C, il sistema crea questi 3 file e fornisce i rispettivi puntatori al programma. I puntatori di questi 3 file (sarebbe piu' corretto definirli canali) sono rispettivamente: stdin, stdout e stderr (dichiarati all'interno di stdio.h). Qualsiasi programma puo' accedere a tali file utilizzando i rispettivi puntatori. Normalmente lo standard output e lo standard error coincidono con il video, ma e' possibile reindirizzare l'output di un programma da video ad un file. Ad esempio il programma ls produce a video l'elenco dei file all'interno di una directory: e' possibile digitare 'ls > pippo' per redirigere tale elenco all'interno del file pippo che verra' creato al momento. Se il programma ls si comporta in modo da distinguere lo standard output dallo standard error, utilizzando quest'ultimo per eventuali messaggi di errore, sara' possibile separare l'output (l'elenco dei file della directory) da eventuali messaggi di errore. Supponendo di eseguire il comando 'ls fintadir', dove fintadir e' una directory inesistente, si otterra' a video un messaggio di errore. Eseguendo il comando 'ls correttadir', dove correttadir e' una directory esistente, si otterra' a video l'elenco dei file contenuti all'interno di correttadir. Ad ogni modo, eseguendo il comando 'ls correttadir > pippo' non otterremo a video alcun output: infatti l'operatore '>' redirige lo standard output nel file pippo. Poiche' non ci sono errori, alla fine il file pippo conterra' l'elenco dei file contenuti nella directory correttadir. La differenza e' visibile eseguendo il comando 'ls fintadir > pippo': in questo caso, poiche' fintadir non esiste, verra' prodotto a video un messaggio di errore, in quanto l'operatore >, per default redirige lo standard output ma non lo standard error. Infatti il programma ls invia l'output corretto allo standard output e gli eventuali errori allo standard error. Quanto visto fino ad ora e' riassumibile nel programma seguente:

#include <stdio.h>

/* cat: concatena file */

int main (int argc, char *argv[])
	{
	 FILE *fp;

	 void filecopy(FILE *, FILE *);

	 char *prog = argv[0];         /* nome del programma */

	 if  (argc == 1 )             /* non ci sono argomenti; copia lo standard input nello standard output*/
		filecopy (stdin, stdout);
	 else
		while (--argc > 0)
			if  ((fp = fopen (*  argv, "r")) == NULL)
				 {
					fprintf (stderr, "%s: impossibile aprire %s\n", prog, *argv);
					exit(1);
			        }
			else
				 {
				  filecopy(fp, stdout);
				  fclose(fp);
				 }

	if (ferror(stdout))
		 {
			fprintf (stderr, "%s: errore di scrittura su stdout\n", prog);
			exit(2);
		 }
	exit(0);
}


/* filecopy: copia file ifp in file ofp */

void filecopy (FILE *ifp, FILE *ofp)
{
	int c;
	
	while ((c = getc(ifp)) != EOF)
		putc(c, ofp);
}

in questo programma eventuali errori vengono indirizzati nello standard error (stderr). La funzione exit termina il programma, chiude i file aperti e ritorna l'argomento al programma chiamante. La funzione ferror(stdout) permette di verificare la presenza di errori sul file (ad esempio spazio su disco esaurito). La funzione ferror() e' cosi' definita:

int ferror (FILE * fp);

dove fp e' un puntatore a FILE e int e' il valore ritornato: NULL se non ci sono errori o diverso da NULL in presenza di errori. La funzione feof() e' simile alla funzione ferror() con la differenza che ritorna un valore diverso da NULL se e' stata raggiunta la fine del file specificato come argomento. Per poter leggere o scrivere delle righe di testo intere, esistono infine le funzioni fgets() e fputs(). La funzione fgets() legge una riga dall'input compreso il carattere newline e la memorizza all'interno del vettore specificato come argomento, terminando con il carattere '\0'. In caso di errore o di fine file, fgets() ritorna NULL, in caso contrario ritorna la riga letta:

char  * fgets (char *line, int maxline, FILE *fp)

dove 'line' e' il vettore che conterra' la riga letta e maxline e' il numero massimo di caratteri della riga da leggere. In output la funzione fgets() ritorna un puntatore al vettore che conterra' la riga letta. La funzione fputs() effettua l'operazione contraria: scrive una stringa (che puo' anche non contenere il newline) all'interno di un file:

int fputs (char *line, FILE *fp)

la funzione fgets() elimina il carattere newline (\n) mentre la funzione fputs() lo aggiunge.

Inizio della guida  Le strutture  Indice  DA FARE

Copyright (c) 2002-2003 Maurizio Silvestri