Skip to content

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:

int scanf(
   const char *format [,argument]...
);

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 con f 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

int n;
printf("Inserire un numero: ");
scanf("%d", &n);

Lettura di un carattere

char carattere;
printf("Inserire un carattere: ");
scanf("%c", &carattere);

Lettura di una stringa

char stringa[10];
printf("Inserire una stringa: ");
scanf("%s", 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:

int printf ( const char * format, ... );

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:

printf("Variabili da stampare: %d %d %f %c", alfa1, alfa2, beta, gamma);

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:

int n;
printf("Numero: %d", n);

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

int n = 50;
printf("Numero: %d\n", n);

Stampa di un carattere

char carattere = 'a';
printf("Il carattere e': %c\n", 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

float valore = 0.78654;
printf("Il calore e': %.2f\n", valore);

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

char s;
printf("Inserire un carattere:");
scanf_s("%c", &s, sizeof(s));

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.