Devo usare fgets o scanf con limitato di ingresso in c?

0

Domanda

Devo usare fgets o formattati scanf come scanf("%10s", foo).

Salvo che scanf non la lettura di caratteri vuoti, che possono essere risolti e infila con scanset, quindi perché dovrei usare fgets invece di scanf?

Qualsiasi aiuto sarebbe apprezzato.


Modifica

Una cosa che voglio chiedere è: anche quando si utilizza fgets cosa succede se l'utente di immettere i caratteri oltre il confine (voglio dire, un sacco di personaggi), non porta a buffer overflow? Quindi come trattare con esso?

c fgets input scanf
2021-11-23 13:53:00
4

Migliore risposta

5

Sulla maggior parte dei sistemi operativi, l'input dell'utente è, per impostazione predefinita, la linea di base. Una ragione di questo è quello di consentire all'utente di premere il tasto backspace per correggere i dati, prima di inviare l'input per il programma.

Per la linea di base di input dell'utente, è significativo e intuitivo per un programma per leggere una riga di input alla volta. Questo è ciò che la funzione fgets non (a condizione che il buffer è abbastanza grande per memorizzare l'intera linea di ingresso).

La funzione scanfinvece, normalmente non si legge una riga di input alla volta. Per esempio, quando si utilizza il %s o %d conversione identificatore di formato con scanfnon consumano un'intera linea di ingresso. Invece, consumerà solo più input corrisponde alla conversione identificatore di formato. Questo significa che il carattere di nuova riga alla fine della linea non sarà in genere consumati (che può facilmente portare a un bug di programmazione). Inoltre, scanf chiamato con il %d conversione identificatore di formato prenderà in considerazione ingresso come 6sldf23dsfh2 valido come ingresso per il numero 6ma tutte le altre chiamate per scanf con lo stesso identificatore di esito negativo, a meno che non si scarta il resto della linea stream di input.

Questo comportamento di scanf è contro-intuitivo, mentre il comportamento di fgets è intuitivo, quando ha a che fare con la linea a base di input dell'utente.

Dopo l'utilizzo fgetsè possibile utilizzare la funzione sscanf sulla corda, per l'analisi del contenuto di una singola linea. Questo vi permetterà di continuare a utilizzare scansets. Oppure si può analizzare la riga con altri mezzi. In ogni modo, fintanto che si sta utilizzando fgets invece di scanf per la lettura dell'input, sarà essere la gestione di una linea di ingresso a un tempo, che è la più naturale ed intuitivo per affrontare basata su riga di input dell'utente.

Quando usiamo fgets cosa succede se l'utente di immettere i caratteri oltre il confine (voglio dire, un sacco di personaggi), non porta a buffer overflow? Quindi come trattare con esso?

Se l'utente immette più caratteri di stare nel buffer come specificato dal secondo fgets argomento della funzione, quindi non sarà un overflow del buffer. Invece, sarà solo l'estratto come molti personaggi del flusso di input in forma nel buffer. È possibile determinare se l'intera linea è stata letta da verificare se la stringa contiene un carattere di nuova riga '\n' alla fine.

2021-11-23 15:13:39

Grazie mille, la tua risposta veramente utile per me.
Becker

Il comportamento di fgets() è solo intuitivo per gli ingressi che non sono più del previsto.
supercat
2

Questo è comunemente discusso argomento, pieno di parere interessante, ma che non di meno. Ho osservato che una grande maggioranza di quelli che hanno già risposto a domande simili su questo sito cadere sul lato della fgets(). Io sono uno di loro. Trovo fgets() per essere molto meglio per l'input dell'utente di scanf() con poche eccezioni. scanf() è considerato da molti come sub-ottimale metodo per la gestione dell'input utente. Per esempio

"...ti dirà se è riuscita o meno, ma posso dire solo circa, dove non è riuscito, e non a tutti come o perché. Si hanno poche opportunità di fare qualche errore di recupero."
(jamesdlin). Ma nell'interesse di tentare equilibrio iniziare citando questa discussione.

L'input di un utente che proviene da stdin, cioè l'input da tastiera, fgets() sarà una scelta migliore. È molto più indulgente che stringa legge può essere convalidato prima della conversione è tentato

Una delle poche volte che utilizza una forma di scanf(): fscanf() sarebbe bene utilizzare sia durante la conversione di input da una sorgente controllata, cioè dalla lettura strettamente formattato il file con la ripetizione di prevedibile campi.

Per maggiori informazioni, questo il confronto dei due mette in evidenza ulteriori vantaggi e gli svantaggi di entrambi.

Edit: per risolvere OP ulteriore domanda su overflow:

"Ancora una cosa che voglio chiedere è: anche quando si usa fgets cosa succede se l'utente di immettere i caratteri oltre il confine (voglio dire un sacco di caratteri), non porta a buffer overflow? Quindi come trattare con esso?"

[fgets()](https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm è ben progettato per prevenire l'overflow del buffer, semplicemente utilizzando i parametri correttamente, ad esempio:

char buffer[100] = {0};
...
while fgets(buffer, sizeof buffer, stdin);  

Questo impedisce l'ingresso maggiore della dimensione del buffer di essere trattati, evitando l'overflow.

anche utilizzando scanf()prevenire buffer overflow è piuttosto semplice: Utilizzare un identificatore di larghezza nella stringa di formato. Se volete leggere l'input, per esempio, e il limite di dimensione di input da utente a 100 caratteri max, il codice è la seguente:

char buffer[101] = {0};// includes space for 100 + 1 for NULL termination

scanf("%100s", buffer);
        ^^^  width specifier 

Tuttavia, con i numeri di overflow non è così bello che utilizza scanf(). Per dimostrare, utilizzare questo semplice codice, inserendo i due valori indicati nel commento uno per eseguire:

int main(void)
{
    int val = 0;
    // test with 2147483647 & 2147483648
    scanf("%d", &val);
    printf("%d\n", val);
    
    return 0;
}

Per il secondo valore, il mio sistema genera il seguente:

NON-FATAL RUN-TIME ERROR: "test.c", line 11, col 5, thread id 22832: Function scanf: (errno == 34 [0x22]). Range error `

Qui potete leggere una stringa, poi seguire con una stringa di conversione numero utilizzando uno dei strto_() funzioni: strtol(), strtod(), ...). Entrambi includono la possibilità per il test di overflow prima di causare un run-time di avviso o di errore. Si noti che l'utilizzo atoi(), atod() non protegge da overflow sia.

2021-11-23 14:10:20

Grazie, apprezzo molto la tua risposta.
Becker

Una domanda "pieno di opinione"? Devo rispettosamente in disaccordo. Non è una questione di opinione che scanf è quasi del tutto inutile, buona al meglio per la lettura semplice ingressi in intro-a-programmi in C, ma molto difficile fare qualcosa di lontanamente sofisticato con — questi sono evidenti fatti! :-)
Steve Summit
1

Finora, tutte le risposte qui presentato complessità di scanf e fgetsma quello che io credo è la pena di ricordare, è che entrambe queste funzioni sono deprecati in corrente C standard. Scanf è particolarmente pericoloso, perché ha tutti i tipi di problemi di sicurezza con i buffer overflow. fgets non è così problematico, ma dalla mia esperienza, tende a essere un po ' goffo e non così utile nella pratica.

La verità è che spesso non si sa bene per quanto tempo l'input dell'utente sarà. È possibile aggirare il problema utilizzando con fgets spero che questo abbastanza grande buffer, ma non è davvero ellegant. Invece, quello che spesso non hanno dinamica del buffer che si crescere per essere abbastanza grande per memorizzare qualunque sia l'input dell'utente consegnerà. E questo è quando getline funzione entra in gioco. È usato per leggere un numero qualsiasi di caratteri dall'utente, fino a quando \n viene rilevato. Essenzialmente, si carica l'intera linea alla tua memoria, come una stringa.

size_t getline(char **lineptr, size_t *n, FILE *stream);

Questa funzione accetta un puntatore a una stringa allocata dinamicamente , come primo argomento, e un puntatore a una dimensione di buffer allocato come secondo argomento e stream come terzo argomento. (sarà essenzialmente luogo stdin lì per input della riga di comando). E ritorna il numero di leggi di personaggi, tra cui \n alla fine, ma non la terminazione null.

Qui, si può vedere l'esempio di utilizzo di questa funzione:

int main() {

printf("Input Something:\n");  // asking user for input

size_t length = 10;                   // creating "base" size of our buffer
char *input_string = malloc(length);  // allocating memory based on our initial buffer size
size_t length_read = getline(&input_string, &length, stdin);  // loading line from console to input_string
// now length_read contains how much characters we read
// and length contains new size of our buffer (if it changed during the getline execution)

printf("Characters read (including end of line but not null at the end)"
       ": %lu, current size of allocated buffer: %lu string: %s"
       , length_read, length, input_string);

free(input_string);    // like any other dynamically-allocated pointer, you must free it after usage
return 0;
}

Naturalmente, l'uso di questa funzione richiede una conoscenza di base sulle puntatori e dinamica della memoria in C, però un po ' più complicata la natura di getline è sicuramente valsa la pena, perché la sicurezza e la flessibilità.

Si può leggere di più su questa funzione, e altre funzioni di input disponibili in C, su questo sito: https://www.studymite.com/blog/strings-in-c Credo che riassume la complessità di ingresso C abbastanza bene.

2021-11-23 19:18:00

Grazie per i tuoi consigli e per il link, mi aiuta molto.
Becker
1

Se avete per esempio un array di caratteri dichiarato come

char s[100];

e voglio leggere una stringa che contiene spazi è possibile utilizzare scanf modo seguente:

scanf( "%99[^\n]", s );

o fgets come:

fgets( s, sizeof( s ), stdin );

La differenza tra questi due bandi è che la chiamata di scanf non leggere il carattere di nuova riga '\n' dal buffer di input. Mentre fgets legge il carattere di nuova riga '\n' se c'è abbastanza spazio nella matrice di caratteri.

Per rimuovere il carattere di nuova riga '\n' che è memorizzato nella matrice di caratteri dopo l'utilizzo fgets si può scrivere per esempio:

s[ strcspn( s, "\n" ) ] = '\0';

Se la stringa di input è più di 99 caratteri quindi le chiamate sia di sola lettura 99 caratteri e aggiungere la sequenza di caratteri con la terminazione carattere zero '\0'. Tutti i caratteri rimanenti saranno ancora nel buffer di input.

C'è un problema con fgets. Per esempio, se prima fgets c'è utilizzata scanf come ad esempio:

scanf( "%d", &x );
fgets( s, sizeof( s ), stdin );

e l'input dell'utente è:

10
Hello World

poi la chiamata di fgets verrà letto solo il carattere di nuova riga '\n' che è memorizzato nel buffer dopo aver premuto il tasto Invio quando il valore intero nella chiamata di scanf è stato letto.

In questo caso è necessario scrivere un codice che consentirà di rimuovere il carattere di nuova riga '\n' prima di chiamare fgets.

Si può fare questo per esempio nel seguente modo:

scanf( "%d", &x );
scanf( " " );
fgets( s, sizeof( s ), stdin );

Se si sta utilizzando scanf quindi, in una tale situazione si può scrivere:

scanf( "%d", &x );
scanf( " %99[^\n]", s );
       ^^ 
2021-11-23 14:05:15

In altre lingue

Questa pagina è in altre lingue

Русский
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
Português
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................