[Avanti]  [Indietro]  [Su]  

7.2.5 Input/output di linea

La terza ed ultima modalità di input/output non formattato è quella di linea, in cui si legge o si scrive una riga alla volta; questa è una modalità molto usata per l'I/O da terminale, ma è anche quella che presenta le caratteristiche più controverse.

Le funzioni previste dallo standard ANSI C per leggere una linea sono sostanzialmente due, gets e fgets, i cui rispettivi prototipi sono:

Le funzioni restituiscono l'indirizzo string in caso di successo o NULL in caso di errore.

Entrambe le funzioni effettuano la lettura (dal file specificato fgets, dallo standard input gets) di una linea di caratteri (terminata dal carattere newline, '\n', quello mappato sul tasto di ritorno a capo della tastiera), ma gets sostituisce '\n' con uno zero, mentre fgets aggiunge uno zero dopo il newline, che resta dentro la stringa. Se la lettura incontra la fine del file (o c'è un errore) viene restituito un NULL, ed il buffer buf non viene toccato. L'uso di gets è deprecato e deve essere assolutamente evitato; la funzione infatti non controlla il numero di byte letti, per cui nel caso la stringa letta superi le dimensioni del buffer, si avrà un buffer overflow, con sovrascrittura della memoria del processo adiacente al buffer.6

Questa è una delle vulnerabilità più sfruttate per guadagnare accessi non autorizzati al sistema (i cosiddetti exploit), basta infatti inviare una stringa sufficientemente lunga ed opportunamente forgiata per sovrascrivere gli indirizzi di ritorno nello stack (supposto che la gets sia stata chiamata da una subroutine), in modo da far ripartire l'esecuzione nel codice inviato nella stringa stessa (in genere uno shell code cioè una sezione di programma che lancia una shell).

La funzione fgets non ha i precedenti problemi di gets in quanto prende in input la dimensione del buffer size, che non verrà mai ecceduta in lettura. La funzione legge fino ad un massimo di size caratteri (newline compreso), ed aggiunge uno zero di terminazione; questo comporta che la stringa possa essere al massimo di size-1 caratteri. Se la linea eccede la dimensione del buffer verranno letti solo size-1 caratteri, ma la stringa sarà sempre terminata correttamente con uno zero finale; sarà possibile leggere i rimanenti caratteri in una chiamata successiva.

Per la scrittura di una linea lo standard ANSI C prevede altre due funzioni, fputs e puts, analoghe a quelle di lettura, i rispettivi prototipi sono:

Le funzioni restituiscono un valore non negativo in caso di successo o EOF in caso di errore.

Dato che in questo caso si scrivono i dati in uscita puts non ha i problemi di gets ed è in genere la forma più immediata per scrivere messaggi sullo standard output; la funzione prende una stringa terminata da uno zero ed aggiunge automaticamente il ritorno a capo. La differenza con fputs (a parte la possibilità di specificare un file diverso da stdout) è che quest'ultima non aggiunge il newline, che deve essere previsto esplicitamente.

Come per le analoghe funzioni di input/output a caratteri, anche per l'I/O di linea esistono delle estensioni per leggere e scrivere linee di caratteri estesi, le funzioni in questione sono fgetws e fputws ed i loro prototipi sono:

Le funzioni ritornano rispettivamente ws o un numero non negativo in caso di successo e NULL o EOF in caso di errore o fine del file.

Il comportamento di queste due funzioni è identico a quello di fgets e fputs, a parte il fatto che tutto (numero di caratteri massimo, terminatore della stringa, newline) è espresso in termini di caratteri estesi anziché di normali caratteri ASCII.

Come per l'I/O binario e quello a caratteri, anche per l'I/O di linea le glibc supportano una serie di altre funzioni, estensioni di tutte quelle illustrate finora (eccetto gets e puts), che eseguono esattamente le stesse operazioni delle loro equivalenti, evitando però il lock implicito dello stream (vedi sez. 7.3.3). Come per le altre forma di I/O, dette funzioni hanno lo stesso nome della loro analoga normale, con l'aggiunta dell'estensione _unlocked.

Come abbiamo visto, le funzioni di lettura per l'input/output di linea previste dallo standard ANSI C presentano svariati inconvenienti. Benché fgets non abbia i gravissimi problemi di gets, può comunque dare risultati ambigui se l'input contiene degli zeri; questi infatti saranno scritti sul buffer di uscita e la stringa in output apparirà come più corta dei byte effettivamente letti. Questa è una condizione che è sempre possibile controllare (deve essere presente un newline prima della effettiva conclusione della stringa presente nel buffer), ma a costo di una complicazione ulteriore della logica del programma. Lo stesso dicasi quando si deve gestire il caso di stringa che eccede le dimensioni del buffer.

Per questo motivo le glibc prevedono, come estensione GNU, due nuove funzioni per la gestione dell'input/output di linea, il cui uso permette di risolvere questi problemi. L'uso di queste funzioni deve essere attivato definendo la macro _GNU_SOURCE prima di includere stdio.h. La prima delle due, getline, serve per leggere una linea terminata da un newline, esattamente allo stesso modo di fgets, il suo prototipo è:

La funzione ritorna il numero di caratteri letti in caso di successo e -1 in caso di errore o di raggiungimento della fine del file.

La funzione permette di eseguire una lettura senza doversi preoccupare della eventuale lunghezza eccessiva della stringa da leggere. Essa prende come primo parametro l'indirizzo del puntatore al buffer su cui si vuole copiare la linea. Quest'ultimo deve essere stato allocato in precedenza con una malloc (non si può passare l'indirizzo di un puntatore ad una variabile locale); come secondo parametro la funzione vuole l'indirizzo della variabile contenente le dimensioni del buffer suddetto.

Se il buffer di destinazione è sufficientemente ampio la stringa viene scritta subito, altrimenti il buffer viene allargato usando realloc e la nuova dimensione ed il nuovo puntatore vengono passata indietro (si noti infatti come per entrambi i parametri si siano usati dei value result argument, passando dei puntatori anziché i valori delle variabili, secondo la tecnica spiegata in sez. 2.4.1).

Se si passa alla funzione l'indirizzo di un puntatore impostato a NULL e *n è zero, la funzione provvede da sola all'allocazione della memoria necessaria a contenere la linea. In tutti i casi si ottiene dalla funzione un puntatore all'inizio del testo della linea letta. Un esempio di codice può essere il seguente:

1:     size_t n = 0;  
2:     char *ptr = NULL; 
3:     int nread; 
4:     FILE * file; 
5:     ...     
6:     nread = getline(&ptr, &n, file); 
e per evitare memory leak occorre ricordarsi di liberare ptr con una free.

Il valore di ritorno della funzione indica il numero di caratteri letti dallo stream (quindi compreso il newline, ma non lo zero di terminazione); questo permette anche di distinguere eventuali zeri letti dallo stream da quello inserito dalla funzione per terminare la linea. Se si è alla fine del file e non si è potuto leggere nulla o c'è stato un errore la funzione restituisce -1.

La seconda estensione GNU è una generalizzazione di getline per poter usare come separatore un carattere qualsiasi, la funzione si chiama getdelim ed il suo prototipo è:

Il comportamento di getdelim è identico a quello di getline (che può essere implementata da questa passando '\n' come valore di delim).


[Avanti]  [Indietro]  [Su]  
© 2000-2003 Simone Piccardi
Pubblicazione web curata da Mirko Maischberger