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