Funzioni di lettura e scrittura
Le funzioni di lettura e scrittura sono varie in C, ma in questa sezione ci concentreremo principalmente su scanf
e printf
.
Funzione di lettura: scanf
Source: cplusplus - scanf
scanf
è una funzione che si occupa di leggere l'input dell'utente e salvarlo in apposite variabili. Questa funzione è bloccante, ovvero blocca l'esecuzione del programma finché l'utente non preme Invio
.
Prototipo
Il prototipo della funzione scanf
è il seguente:
Parametri in input
I parametri della funzione scanf
si dividono in due parti:
- una serie di caratteri che specificano il tipo di dato da leggere;
- l'indirizzo della variabile in cui memorizzare tale dato.
Tipo di dato
Il tipo di dato è definito da una serie di caratteri detta “stringa formattatore” o “identificatre”, format identifier in inglese, che di base, assume la forma:
%specifier
Tipo di dato | Tipo di variabile | Identificatore |
---|---|---|
Intero (con segno) | `int` | %d |
Reale (con segno) | `float` | %f |
Carattere | `char` | %c |
Stringa | `char[]` | %s |
Intero esadecimale | `int` | %x |
Si possono aggiungere altri tre campi all'identificatore:
%[*][width][length]specifier
dove gli elementi tra parentesi quadre sono opzionali e quindi non obbligatori. In particolare:
- lentgh: permette di creare altri tipi di dato oltre a quelli base, ad esempio
l
indica long e conf
genera%lf
, ovvero l'identificatore per double.%ld
, invece, sta per long int; - width: indica quanti caratteri leggere da quelli inseriti;
- *: indica di non memorizzare in alcuna variabile ciò che è letto.
Indirizzo della variabile
Oltre all'identificatore, la scanf
necessita di sapere dove memorizzare il dato che legge. Ogni variabile in RAM occupa il proprio posto definito da un indirizzo di memoria. Per ricavare l'indirizzo di una variabile in C si usa il nome della variabile
preceduta dal simbolo &
.
Sulla lettura delle stringhe:
Le stringhe, in quanto costituite da più caratteri, in gergo array, non necessitano della &
in fase di lettura. Per questo tipo di variabili, infatti, il nome si riferisce già all'indirizzo in cui è conservata la variabile.
Valore di ritorno
La scanf
ritorna un intero che indica il numero di dati letti in input e correttamente assegnati alle variabili specificate. Se si presenta un errore di lettura o il termine del file eof
, la scanf
ritorna 0. In questo caso sono impostati i valori dei flag di errore che possono essere controllati tramite feof
oppure ferror
. Se L'errore avviene prima che alcun dato possa essere letto con successo, scanf
ritorna EOF
.
Esempi di utilizzo
Lettura di un dato intero
Lettura di un carattere
Lettura di una stringa
Funzione di scrittura: printf
Source: cplusplus - printf
La funzione printf
stampa a video un messaggio. Se questo contiene un identificatore per un tipo di dato, la printf
si aspetterà di ricevere anche il nome delle variabili da stampare in output. Come nella scanf
, ad ogni identificatore corrisponde un tipo di variabile ben preciso. Gli identificatori sono gli stessi della scanf
(es. %d
per intero).
Dopo la stampa con printf
, il terminale non va a capo automaticamente, per cui si può ovviare a questo problema terminando il messaggio con il carattere \n
, detto anche terminatore di linea (n sta per newline).
Prototipo
Il prototipo della funzione printf
è il seguente:
Parametri in input
La printf
si aspetta il messaggio da stampare che deve essere compreso (includendo anche il \n
) tra le doppie virgolette. Questo messaggio rappresenta una stringa nel linguaggio C, indicata dal tipo char *
nel prototipo. Se nella stringa compaiono gli identificatori di tipo, alla printf
bisognerà passare in input anche i nomi delle variabili il cui tipo corrisponde agli identificatori inseriti. In una stringa si possono avere numerosi identificatori e di tipo diverso.
Attenzione!
Con la printf
bisogna prestare molta attenzione agli identificatori e alle variabili associate per due motivi:
1) Gli identificatori agiscono da “segnaposto”. Ciò significa che al primo identificatore inserito nel messaggio sarà associata la prima variabile specificata dopo il messaggio; al secondo identificatore la seconda variabile in lista, al terzo identificatore la terza e così via. Ad esempio:
Il primo %d
si riferisce a alfa1
, quindi la printf
si aspetterà che alfa1
sia di tipo intero e lo stesso vale per alfa2
. Il %f
va con la terza variabile elencata, beta
che quindi dovrà essere di tipo float
. Infine con gamma
, se si vuole stampare il carattere che contiene bisognerà usare %c
come in questo caso. Per questo motivo è utile prestare attenzione all'ordine in cui si specificano le variabili dopo il messaggio. Ogni variabile deve essere associata all'identificatore del tipo corretto per non avere problemi durante la stampa.
2) Quando si stampa una variabile a video, prima di stamparla, è utile controllare che la variabile sia stata in qualche modo inizializzata, sia tramite assegnamento diretto che tramite lettura con scanf
. In questo esempio:
la variabile n non è stata inizializzata. In questo caso, la variabile non conterrà un valore significativo, ma un “residuo” di memoria da una elaborazione precedente. È quindi utile controllare che nel codice sia assegnato un valore a ciascuna variabile prima di utilizzarle in stampe, o peggio, in operazioni aritmetiche o di controllo.
Valore di ritorno
La printf
ritorna un numero intero (da int
nel prototipo). Se la stampa avviene con successo, questo rappresenta il numero di variabili correttamente stampate a video. In caso contrario, è impostato il valore del flag ferror
e printf
ritorna un numero negativo.
Esempi di utilizzo
Stampa di un numero intero
Stampa di un carattere
Stampa del valore intero associato ad un carattere
Se si usa l'identificatore %d
con una variabile di tipo char
, al posto di stampare a video un carattere, il programma stamperà l'intero associato al carattere:
char carattere = 'a';
printf("Il carattere e': %d\n", carattere); //Stamperà 92, ovvero 'a' in ASCII
Stampa solo delle prime 2 cifre decimali di un numero reale
Stampa di variabili di tipi diversi
int n_voti = 4;
float media = 7.5;
printf("Ad oggi ho preso %d voti e la mia media e' %f\n", n_voti, media);
Funzione di lettura sicura: scanf_s
La funzione scanf_s
rende la lettura sicura contro gli attacchi di tipo buffer overflow. Questo attacco sfrutta la lettura di stringhe con %s
e consiste nell'inserire una stringa più lunga di quella che il programma si aspetta (da qui il termine “overflow”). Se il numero di caratteri non è controllato, infatti, l'attaccante potrebbe inserie delle stringhe arbitratiamente lunghe che possono contenere caratteri speciali tra cui i codici per eseguire una shell con privilegi di amministratore.
Per ovviare a questa falla, la scanf_s
tronca la stringa letta in input alla dimensione della variabile letta.
Parametri in input
La scanf_s
prende in input tre parametri:
- L'identificatore della stringa da leggere;
- L'indirizzo della variabile in cui memorizzare il dato letto;
- La dimensione dei dati da leggere.
La dimensione può essere ricavata tramite la funzione sizeof(variabile)
che prende in input una variabile e restituisce la dimensione in byte della variabile stessa.
Esempio di utilizzo
Funzione di scrittura sicura: printf_s
Source: stackoverflow
A differenza della printf
, la funzione printf_s
effettua dei controlli sulla stringa da stampare a video. In particolare, verifica che le sia passata effettivamente una stringa da stampare e non una stringa nulla. Inoltre, se compare %s
nella stringa passata a printf_s
si controlla che ci sia una variabile associata elencata dopo il messaggio da stampare. Ad esempio:
printf("Stampa: %d, %s", intero); //Esegue lo stesso
printf_s("Stampa: %d, %s", intero); //Restituisce errore perché intero corrisponde a %d e non c'è nessuna variabile elencata da associare a %s
Se uno di questi controlli fallisce, la printf_s
blocca la stampa del messaggio a video e non prosegue ulteriormente.