Timeline
Chat
Prospettiva
Scanf
Da Wikipedia, l'enciclopedia libera
Remove ads
scanf (abbreviazione dell'inglese scan formatted) è una funzione che viene implementata in diversi linguaggi di programmazione. Viene utilizzata per l'inserimento di dati formattati (caratteri e numeri). Da essa derivano inoltre altre funzioni, tra le cui più citate fscanf e sscanf.
Ha origine dal linguaggio C dove è inclusa nella libreria stdio.h, ma queste funzioni sono precedenti a C[1] e possiedono altri nomi, come ad esempio readf in ALGOL 68.
Remove ads
Storia
La funzione è stata implementata da Mike Lesk[2], assieme al resto della libreria cui appartiene, la quale è stata poi ufficialmente resa parte di Unix dalla versione 7 (1979).
Utilizzo
Riepilogo
Prospettiva
La funzione ha il seguente prototipo:
int scanf(const char *format, ...);
Dal C99 e a seguire è stato introdotto restrict[3] con i definitori (vedi anche restrict):
int scanf(const char * restrict format, ...);
La funzione legge dei caratteri dal canale standard di input (stdin[4]), solitamente la tastiera, li converte secondo le specifiche di formattazione fornite dall'argomento format e memorizza i valori ottenuti negli argomenti seguenti in concordanza del tipo di dato richiesto.[5] La funzione si ferma quando esaurisce la propria stringa di formattazione o quando alcuni dati in ingresso non soddisfano le specifiche di conversione. Il valore restituito è un numero intero che indica quanti valori sono stati estratti correttamente: negativo se si è verificato un errore, positivo se c'è stata una lettura e in particolare 0 (zero) se non è stata fatta alcuna assegnazione perché il primo carattere letto non è conforme alla prima specifica della stringa di formato.
Similarmente alla funzione printf, il ... sta ad indicare che si tratti di una funzione variadica (che può assumere un numero variabile di parametri).
Sintassi
La sintassi generica da seguire per chiamare la funzione è:
scanf (tipo, &arg);
Da notare come si debba fornire l'indirizzo di memoria di arg (tramite l'operatore unario &) in cui verranno memorizzati i dati. A ciò fa però eccezione il definitore %s perché, in quanto array di caratteri, è già un puntatore: nella dichiarazione di un array infatti, viene memorizzato l'indirizzo di memoria del primo elemento che lo compone, pertanto, dato ad esempio un vettore strvet[37], le due espressioni *strvet e strvet[0] sono equivalenti.[6]
Qui di seguito è riportato un esempio in C
#include <stdio.h>
/* Libreria standard per l'I/O */
int main() {
int n;
printf("Inserisci il valore di N: ");
scanf("%d", &n);
printf("N al quadrato è uguale a %d\n", n*n);
return 0;
}
Nell'esempio proposto viene richiesto all'utente un numero intero e viene calcolato il quadrato di tale numero.
La funzione scanf ha lo scopo di rilevare quanto immesso dall'utente tramite la tastiera, convertirlo in un numero intero e memorizzarne il risultato nella variabile n.
La lettura si blocca appena incontra un carattere non conforme a quello definito dal definitore di formato.
Quindi, in questo esempio, fornendo un input del tipo 435 9483 9342 oppure 435ute, si otterrebbe in entrambi i casi 435*435 come output (a schermo), lasciando nel buffer di memoria il resto della stringa, che potrebbe essere letta da un'altra funzione o dalla stessa, se inserita in un ciclo apposito. Bisogna pertanto prestare attenzione a questo fattore perché potrebbe creare incongruenze con le altre funzioni che accederebbero successivamente al buffer.
L'argomento format
L'argomento format è una stringa che specifica l'interpretazione dell'input e può contenere una o più di:
- Spazi vuoti: vuoto , tabulazioni
\to nuova riga\ne\re anche le rispettive traduzioni numeriche ASCII, compreso lo\0(zero di fine stringa). Un carattere di spazio vuoto comporta chenlegga, ma non memorizzi, tutti i caratteri di spazio vuoti consecutivi nell'input fino al successivo carattere diverso da uno spazio vuoto; - Caratteri diversi da uno spazio vuoto, ad eccezione del segno di percentuale (
%). Un carattere diverso da uno spazio vuoto comporta chescanflegga, ma non memorizzi, un carattere diverso da uno spazio vuoto corrispondente. Se il carattere successivo nel flusso di input non corrisponde,scanftermina; - Specifiche di formato introdotte dal segno di percentuale (
%). Una specifica di formato determina la lettura e la conversione dei caratteri nell'input in un valore di un tipo specificato. Il valore viene assegnato a un argomento nell'elenco degli argomenti.
I definitori di formato (format specifier)
Una specifica di formato presenta la forma seguente[7]:
%[*][ampiezza][{h|l|L}]tipo
Come accade anche nella funzione printf, gli argomenti tra parentesi possono essere omessi a seconda delle esigenze.
La ampiezza è un numero intero positivo che controlla il numero massimo di caratteri da leggere da stdin per quella specifica di formato.
Il carattere di soppressione dell'assegnazione (*) serve a leggere un valore dall'input e a ignorarlo, senza pertanto assegnarlo a una variabile.
Nella tabella seguente sono mostrati i tipi di dato[8] da poter inserire:
Una differenza che si ritrova con la funzione printf sta nell'utilizzo dei definitori %i, %d, %u, %o, e %x, in particolare per %i e %d perché sono sinonimi per l'output, ma diversi se usati per l'input. Bisogna pertanto fare attenzione al tipo di numero intero fornito. Mentre %i è in grado di riconoscere una base decimale da una ottale e da una esadecimale (rispettivamente tramite l'uso del prefisso 0 e 0x), in tutti gli altri casi si sta definendo a prescindere una base specifica e un eventuale segno[9].
Il definitore %l (long, cioè lunghezza doppia) viene sempre seguito da altri definitori per indicare che lo spazio di memoria necessario da allocare (fa unica eccezione p perché, in quanto già indirizzo di memoria, non ha bisogno di una ulteriore specifica di formato). Pertanto, ad esempio, %lf indicherà un numero reale a doppia precisione (double), mentre %lu indicherà un numero intero decimale senza segno a 64 bit (invece degli usuali 32 bit).
Il definitore %h (short, cioè mezza lunghezza) si antepone solo per i definitori di numeri interi. Pertanto, ad esempio, %hi indicherà un numero intero a 16 bit.
Il definitore %L si antepone ai numeri reali in virgola mobile (es. %Lf) per indicare i formati long double.
Il definitore %g ha la proprietà di poter leggere i numeri in virgola mobile sia del formato %f, che del formato %e. Proprietà che nella funzione printf si traduce nel poter restituire il formato che si presenta più corto tra i due.
Il definitore %n punta a un tipo di dato int, in cui viene memorizzato il numero di caratteri letti correttamente dallo stream o dal buffer fino a quel punto nella chiamata a scanf().
Il definitore %p ha la particolarità di dover puntare a un puntatore che è stato dichiarato, o per lo meno convertito, come void.
Per l'uso degli insiemi di caratteri ([]), si può complementarizzare tramite l'insieme di caratteri scelti: se il primo carattere nell'insieme è un accento circonflesso (^), l'effetto viene invertito e il campo di input viene letto fino al primo carattere che appartiene nel resto dell'insieme di caratteri (operando a tutti gli effetti l'operazione XOR).
Il definitore %% corrisponde al solo carattere di percentuale (%).[10]
Remove ads
Valore restituito
La funzione scanf() restituisce il numero (int) di campi che sono stati convertiti e assegnati correttamente. Il valore di ritorno non comprende il numero di campi che sono stati letti, ma non assegnati.
Il valore di ritorno è negativo, EOF (end of file), per un tentativo di lettura alla fine del file, se non è stata eseguita alcuna conversione. Un valore di ritorno 0 (zero) indica che non sono stati assegnati campi.[11]
Condizioni di errore
Riepilogo
Prospettiva
Se il tipo di argomento a cui deve essere assegnato è diverso dalla specifica di formato, possono verificarsi risultati imprevedibili. Ad esempio, la lettura di un valore a virgola mobile, ma l'assegnazione in una variabile di tipo int, non è corretta e avrebbe risultati imprevedibili.
Se ci sono più argomenti che specifiche di formato, gli argomenti supplementari vengono ignorati. I risultati non sono definiti se non vi sono argomenti sufficienti per le specifiche di formato.
Se la stringa di formato contiene una specifica di formato non valida e vengono utilizzate specifiche di formato posizionali, errno verrà impostato su EILSEQ.
Se vengono utilizzate specifiche di formato posizionale e non vi sono argomenti sufficienti, errno verrà impostato su EINVAL.
Se si verifica un errore di conversione, errno può essere impostato su ECONVERT.[12]
Overflow aritmetico
Durante la conversione da stringa a numero (come con atoi, atol e atof) vi è il problema per cui la funzione scanf non riesce a determinare un overflow aritmetico (allo stesso modo di un underflow aritmetico nel caso dei numeri in virgola mobile). Perciò viene sconsigliato l'uso di questi convertitori per qualsiasi software applicativo che richieda controlli stringenti sull'input.
Come alternativa si indicano le funzioni strtol e strtod, che effettuano dei controlli aggiuntivi sull'overflow/underflow.
L'applicativo per l'analisi statica del codice[13] in Clang (Clang-Tidy) sconsiglia dall'uso di scanf.[14]
Remove ads
Funzioni derivate
fscanf
La funzione fscanf legge i dati di input da un file, piuttosto che utilizzare l'input standard. Il suo funzionamento è sostanzialmente uguale a quello della sua primitiva e il suo prototipo (in C/C++) è:
int fscanf (FILE *file, const char *format, ...); // fino a C99
int fscanf (FILE *restrict file, const char *restrict format, ...); //dal C99
sscanf
La funzione sscanf legge i dati di input da un buffer, piuttosto che utilizzare l'input standard, restituendo i valori che si possono leggere, e il suo prototipo (in C/C++) è:
int sscanf (char *buffer, const char *format, ...); // fino a C99
int sscanf (char *restrict buffer, const char *restrict format, ...); //dal C99
Altre funzioni derivate
Dal C11 sono state introdotte ulteriori funzioni: scanf_s, fscanf_s, sscanf_s.
Remove ads
Esempi d'uso
Riepilogo
Prospettiva
#include <stdio.h>
int main(void)
{
int i;
float fp;
char c, s[81];
printf("Inserire un numero intero, un numero reale,"
" un carattere e una stringa: \n");
if (scanf("%d %f %c %s", &i, &fp, &c, s) != 4)
printf("Non tutti i campi sono stati compilati\n");
else
{
printf("numero intero = %d\n", i);
printf("numero reale = %f\n", fp);
printf("carattere = %c\n", c);
printf("stringa = %s\n",s);
}
}
Se l'input è 27 5.4 t si, allora l'uotput sarà simile a:
Inserire un numero intero, un numero reale, un carattere e una stringa:
numero intero = 27
numero reale = 5.400000
carattere = t
stringa = si
Uso dell'indicatore d'ampiezza
#include <stdio.h>
int main()
{
int i;
float x, y;
char name[50];
printf ("Inserire due stringhe numeriche che contengano 8 caratteri"
" ognuna e che abbiano entrambe un punto al centro:\n");
scanf("%2d %f %6f %[0123456789]", &i, &x, &y, name);
printf("numero intero = %d\n", i);
printf("primo numero reale = %f\n", x);
printf("secondo numero reale = %f\n", y);
printf("il resto della stringa = %s\n",name);
}
Se l'input è 1234.4321 1234.4321, allora l'output sarà simile a
Inserire due stringhe numeriche che contengano almeno 8 caratteri ognuna e che abbiano entrambe un punto al centro:
numero intero = 12
primo numero reale = 34.432098
secondo numero reale = 1234.400024
resto della stringa = 321
Come si può notare, l'uso di uno definitore di virgola mobile permette la lettura del punto di separazione decimale, ma bisogna contare anche la lettura del punto perché è esso stesso un carattere (il 24 in coda al secondo numero reale è solo un errore di conversione binaria dei numeri decimali).
Uso in un ciclo
#include <stdio.h>
int main()
{
int numero;
printf("Inserire un numero esadecimale o qualunque altra cosa diversa per uscire:\n");
while (scanf("%x",&numero))
{
printf("Numero esadecimale = %x\n",numero);
printf("Numero decimale = %d\n",numero);
}
}
Questo esempio converte un numero intero esadecimale in un numero intero decimale. Il loop while termina se il valore di input non è un numero intero esadecimale. Se l'input è 0x231 0xf5e 0x1 0x, allora l'output sarà simile a
Inserire un numero esadecimale o qualunque altra cosa diversa per uscire:
Numero esadecimale = 231
Numero decimale = 561
Numero esadecimale = f5e
Numero decimale = 3934
Numero esadecimale = 1
Numero decimale = 1
Numero esadecimale = 0
Numero decimale = 0
Uso del soppressore d'assegnazione (*)
#include <stdio.h>
int main()
{
char text[20];
float number;
printf("Inserire i dati:\n");
scanf("%s %*d %f", text, &number);
printf("output: %s %f\n", text, number);
return 0;
}
Al momento di leggere la stringa, scanf non leggerà ciò che corrisponde a %*d, pertanto non c'è bisogno di assegnare l'indirizzo di un argomento per tale corrispondenza.[15] Pertanto, per un input del tipo prova 324.523, si otterrà un output del tipo:
Inserire i dati:
output: prova 0.523000
Uso dei puntatori
#include <stdio.h>
int main()
{
int val = 123;
char buf[100];
sprintf(buf, "%p", (void*)&val);
printf("Original =%s\n", buf);
void *ptr;
sscanf(buf, "%p", &ptr);
printf("Read back=%p\n", ptr);
int *iPtr = ptr;
if (iPtr == &val) {
printf("Pointers match\n");
}
}
La funzione sprintf alla riga 3 scrive il puntatore nel buffer di output; sscanf lo rilegge.
Alla riga 6, la stringa immessa in sscanf corrisponde esattamente alla stringa prodotta da sprintf, quindi lo standard garantisce che iPtr == &val verrà valutato come true e il comportamento sia pertanto definito.
Passare una stringa che non corrisponde a nulla di ciò che è stato prodotto dallo stesso programma in esecuzione sarebbe un comportamento indefinito.[16]
Remove ads
Vulnerabilità
scanf è vulnerabile ai format string attacks[17]. Bisogna pertanto prestare grande cura nell'assicurarsi che la stringa di formattazione contenga delle limitazioni riguardo la grandezza degli array (e quindi anche delle stringe) che vengono passati. In molti casi è arbitraria la grandezza della stringa inserita dall'utente e non può essere determinata prima che scanf venga eseguita. Ciò significa che i definitori di formato usati senza un limitatore di lunghezza sono intrinsecamente non sicuri e sfruttabili tramite buffer overflows.
Un'ulteriore vulnerabilità riguarda il permesso ad usare stringhe di formato dinamiche (dynamic formatting strings[18]), per esempio stringhe di formattazione contenute in file di configurazione o altri file accessibili dall'utente e di cui ne detenga i permessi di modifica. Un attacco noto che sfrutta questo genere di vulnerabilità è SQL injection.
Per questi ed altri motivi, ad oggi scanf viene messa in secondo piano durante l'apprendimento del C/C++, propendendo piuttosto verso fgets per l'acquisizione della riga, per poi passare la stessa a sscanf per scomporla (entrambe funzioni anch'esse contenute all'interno di stdio.h).[19]
Remove ads
Note
Voci correlate
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads