Corso di C con Linux

Lettura e scrittura di caratteri

 

Le operazioni di ingresso/uscita (input/output, spesso indicati con la sigla I/O), ovvero lo scambio di informazioni tra un programma e il mondo esterno al calcolatore, sono generalmente le parti meno eleganti di un algoritmo, a causa della complessità, sia logica che fisico-architetturale, della modalità di interscambio con l’esterno, ivi compresa la necessità di provvedere al recupero di eventuali errori (ad es. il file che si sta cercando di aprire non esiste).

La specifica del linguaggio C non prevede espressamente nessuna funzionalità riguardo le operazioni di I/O, tuttavia, poiché un programma C deve essere eseguito sotto il controllo di un sistema operativo, i programmatori dei vari sistemi hanno previsto un certo numero di funzioni che, pur non previste inizialmente, di fatto sono diventate una dotazione standard di ogni compilatore C.

Le funzioni per le operazioni di I/O, che sono generalmente costituite da procedure codificate in linguaggio assembler e fanno parte del “corredo” di tutti i compilatori, sono raccolte in una libreria standard, contenuta nel file header “stdio.h”, che i programmi C devono necessariamente includere ogni qualvolta devono colloquiare con il mondo esterno, inclusi la tastiera e il terminale video.

Tralasciando tutti i dettagli relativi a questa problematica, per il momento diamo un primo sguardo a due semplici funzioni: getchar(), che legge un carattere dalla tastiera, e putchar(), che scrive un carattere sul video.


In realtà queste due funzioni leggono e scrivono, rispettivamente, dallo standard input e sullo standard output, e questi sono associati per default alla tastiera e al video.

La funzione getchar() è così definita:

int getchar(void)

ovvero, non richiede alcun parametro d’ingresso e restituisce un valore di tipo int, che rappresenta la codifica ASCII del carattere letto.
La funzione putchar(), duale della precedente, è così definita:

int putchar(char c)

ovvero richiede come parametro d’ingresso il carattere da scrivere e restituisce un valore di tipo int, che rappresenta la codifica ASCII del carattere scritto (tale valore è non significativo e nei programmi viene spesso ignorato).

Come primo esempio, scriviamo un programma che legge un carattere da tastiera e lo stampa sul video:

/*
 * File lez36_1.c
 */
#include <stdio.h>

int main()
{
       int c;

       c = getchar();
       (void)putchar((char)c);

return 0;
}


Compilando ed eseguendo questo esempio, si può osservare un comportamento diverso da quello atteso.
Infatti, leggendo il codice sorgente ci si aspetta che l'esecuzione del programma attenda un carattere da tastiera, lo stampi su video ed esca.
Invece possiamo vedere che il programma attende sempre un altro carattere, finché non viene premuto il tasto [INVIO].

Questo comportamento deriva dal modo di gestire l'I/O da parte di Linux (e dei sistemi UNIX in generale).
Linux memorizza in un'area temporanea (un buffer) tutti i caratteri provenienti da tastiera, fino all'arrivo di un carattere di ritorno a capo, dopodiché li passa nello stesso ordine al programma.
Altri sistemi adottano invece una soluzione differente, più in linea, se si vuole, con le nostre attese.

Questo esempio ci permette di sottolineare che le funzioni di I/O non fanno parte del linguaggio C, ma sono fortemente influenzate dalle caratteristiche dell'ambiente di esecuzione.

Sotto questo aspetto, l'esempio successivo è molto interessante:

/*
 * File lez36_2.c
 */
#include <stdio.h>

int main()
{
       int c;

       while ((c = getchar()) != 'q')
             putchar((char)c);

return 0;
}



Una possibile esecuzione è la seguente (in grassetto sono indicati i caratteri immessi dall'utente, in corsivo quelli stampati dal programma):

riga normale
riga normale
altra riga normale
altra riga normale
riga con una q

Notiamo che ogni riga immessa viene stampata due volte, mentre l'ultima non viene ripetuta dal programma.
Le righe in grassetto sono mostrate perché la shell di Unix ripete sul video ogni carattere immesso dalla tastiera.
Quando si preme il tasto [INVIO], le funzione getchar() ritorna il contenuto di tutto il buffer, che viene stampato da putchar().
Poiché l'ultima riga contiene il carattere 'q', la condizione dell'istruzione while diventa falsa e putchar() non viene eseguito (per nessuno dei caratteri della riga!).

Notiamo ancora una differenza tra le istruzioni putchar() dei due esempi: il casting al tipo void nel primo programma non è necessario, in quanto non modifica il comportamento della funzione; l'unica utilità di questa operazione è quella di indicare esplicitamente che stiamo ignorando il valore ritornato da putchar().

Infine un esercizio:
Create un programma che esegua un ciclo che, facendo uso delle istruzioni while e if (o switch), stampino a video tutto il set ASCII, fornendo, per i caratteri non stampabili, un'indicazione del loro significato (simile a quanto mostrato dalla pagina man ascii).

 

Torna all'indice Generale del corso di Corso di C con Linux di Software Planet