Ar trebui să folosesc fgets sau scanf cu un număr limitat de intrare în c?

0

Problema

Ar trebui sa folosesc fgets sau formatate scanf ca scanf("%10s", foo).

Cu excepția că scanf nu citește caractere goale, care pot fi rezolvate și de a face mai multe umpluturi cu scanset, atunci de ce ar trebui să utilizați fgets în loc de scanf?

Orice ajutor ar fi apreciat.


Edit

Un singur lucru mai vreau sa va intreb este: chiar și atunci când folosim fgets ce se întâmplă dacă utilizatorul introduce caractere mai mult decât limita (adică o mulțime de personaje), nu-l duce la buffer overflow? Atunci cum să se ocupe cu ea?

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

Cel mai bun răspuns

5

Pe cele mai multe sisteme de operare, utilizator de intrare este, în mod implicit, bazat pe linie. Un motiv pentru acest lucru este de a permite utilizatorului să apăsați tasta backspace pentru a corecta intrare, înainte de a trimite date de intrare pentru program.

Pentru conformitate pe baza introduse de utilizator, este semnificativ și intuitivă pentru un program pentru a citi o linie de intrare la un moment dat. Aceasta este ceea ce funcția fgets nu (în cazul în care buffer-ul este suficient de mare pentru a stoca întreaga linie de intrare).

Funcția scanfpe de altă parte, în mod normal, nu citește o linie de intrare la un moment dat. De exemplu, atunci când utilizați %s sau %d formatul de conversie specificator cu scanf, acesta nu va consuma o intreaga linie de intrare. În schimb, acesta va consuma la fel de mult de intrare ca meciurile de conversie specificator de format. Acest lucru înseamnă că newline la capăt de linie în mod normal nu pot fi consumate (care poate duce cu ușurință la bug-uri de programare). De asemenea, scanf numit cu %d formatul de conversie specificator va lua în considerare de intrare, cum ar fi 6sldf23dsfh2 ca intrare valabil pentru numărul 6dar nici noi apeluri pentru scanf cu aceeași specificator va eșua, dacă nu vă aruncați restul de linii din fluxul de intrare.

Acest comportament de scanf este contra-intuitiv, întrucât comportamentul fgets este intuitiv, atunci când se ocupă cu linie bazate pe date introduse de utilizator.

După utilizarea fgets, puteți utiliza funcția sscanf pe șir, pentru parsarea cuprins de o linie individuală. Acest lucru vă va permite să continuați să utilizați scansets. Sau poate analiza linia de prin alte mijloace. Oricum, atâta timp cât utilizați fgets în loc de scanf pentru citirea de intrare, va ocupa o linie de intrare la un moment dat, care este natural și intuitiv mod de a face cu linia bazate pe date introduse de utilizator.

Când ne-am folosi fgets ce se întâmplă dacă utilizatorul introduce caractere mai mult decât limita (adică o mulțime de personaje), nu-l duce la buffer overflow? Atunci cum să se ocupe cu ea?

Dacă utilizatorul introduce mai multe caractere decât a se potrivi în zona-tampon după cum este specificat de cel de-al doilea fgets funcția de argument, atunci acesta nu va preaplin tampon. În schimb, se va extrage doar ca multe personaje din fluxul de intrare ca se potrivesc în tampon. Puteți determina dacă întreaga linie a fost citit de verificare dacă șirul conține un caracter newline '\n' la sfârșitul anului.

2021-11-23 15:13:39

Multumesc mult, raspunsul tau foarte util pentru mine.
Becker

Comportamentul fgets() este doar intuitiv pentru intrări care nu sunt mai mult de așteptat.
supercat
2

Acesta este un frecvent subiect discutat, plin de opinie, dar interesant, nici unul mai puțin. Am observat că o mare parte dintre cei care deja au răspuns la întrebări similare de pe acest site se încadrează pe partea de fgets(). Eu sunt unul dintre ei. Am găsit fgets() pentru a fi mult mai bine să utilizați pentru introduse de utilizator decât scanf() cu câteva excepții. scanf() este considerat de mulți ca sub-optimă metodă de manipulare de intrare de utilizator. De exemplu

"...vă va spune dacă a reușit sau nu a reușit, dar pot să vă spun doar aproximativ în cazul în care acesta nu a reușit, și nu la toate cum sau de ce. Ai au foarte puține șanse de a face orice eroare de recuperare."
(jamesdlin). Dar în interesul încercarea de echilibru, va începe citând această discuție.

Pentru o intrare de utilizator, care vine de la stdin, adică intrare de la tastatură, fgets() va fi o alegere mai bună. Este mult mai iertator, în care șirul se citește pot fi pe deplin validate înainte de conversie este de încercat

Una dintre cele câteva ori, folosind o formă de scanf(): fscanf() ar fi bine să utilizați ar putea fi atunci când conversia de intrare de la o sursă controlată, adică la o lectură strict fișier formatat cu repetarea previzibil domenii.

Pentru mai multe discutii, această comparație dintre cele două repere suplimentare avantaje și dezavantaje de ambele.

Edit: la adresa OP întrebare suplimentară despre overflow:

"Un singur lucru mai vreau sa va intreb este: chiar și atunci când folosim fgets ce se întâmplă dacă utilizatorul introduce caractere mai mult decât limita (adică o mulțime de caractere), nu-l duce la buffer overflow? Atunci cum să se ocupe cu ea?"

[fgets()](https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm este frumos proiectat pentru a preveni buffer overflow, pur și simplu prin utilizarea parametrilor săi în mod corespunzător, de exemplu:

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

Acest lucru previne de intrare mai mare decât dimensiunea buffer-ului de a fi prelucrate, prevenind astfel preaplin.

chiar folosind scanf()prevenirea buffer overflow este destul de drept înainte: Utilizați o lățime specificator în șir format. Dacă doriți să citiți de intrare, de exemplu, și o limită de mărime de intrare de la utilizator la 100 de caractere, codul ar include următoarele:

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

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

Cu toate acestea, cu numere, de preaplin nu este atât de frumos, folosind scanf(). Pentru a demonstra, utilizați acest cod simplu, doar introduceți cele două valori indicate în comentariu pe run:

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

Pentru cea de-a doua valoare, sistemul meu aruncă următoarele:

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

Aici aveți nevoie pentru a citi într-un șir, apoi urmați cu o coardă la numărul de conversie, folosind unul dintre strto_() funcții: strtol(), strtod(), ...). Ambele includ capacitatea de a testa pentru preaplin înainte provocând un run-time de avertizare sau de eroare. Rețineți că utilizarea atoi(), atod() nu va proteja de preaplin fie.

2021-11-23 14:10:20

Multumesc, apreciez raspunsul tau.
Becker

O întrebare "plin de opinie"? Trebuie să te contrazic. Nu e o chestiune de opinie care scanf este aproape complet inutil, bun la mai bun pentru a citi singur, simplu intrări în intro-pentru-C programe, dar prohibitiv de dificil de a face ceva sofisticat, cu — aceste sunt fapte evidente! :-)
Steve Summit
1

Până în prezent, toate răspunsurile prezentat aici, complexitatea scanf și fgetsdar ceea ce cred că este demn de menționat, este faptul că ambele funcții sunt depreciate în curent C standard. Scanf este deosebit de periculos, pentru că are tot felul de probleme de securitate cu buffer overflow. fgets nu este la fel de problematică, dar din experienta mea, acesta tinde să fie un pic greoaie și nu-așa-utile în practică.

Adevărul este că, de multe ori nu știi cu adevărat cât de mult datele introduse de utilizator vor fi. Puteți obține în jurul valorii de acest lucru prin utilizarea fgets cu sper că acest lucru va suficient de mare tampon, dar asta nu e chiar litorala. În schimb, ceea ce de multe ori vreau să fac este de a avea dinamice tampon care va crește să fie suficient de mare pentru a stoca orice date introduse de utilizator va livra. Și acest lucru este atunci când getline funcția intră în joc. Este folosit pentru a citi orice număr de caractere de la utilizator, până când \n este întâlnită. În esență, se încarcă întreaga linie pentru memorie, ca un șir de caractere.

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

Această funcție are un pointer la un alocate dinamic șir ca primul argument, și un pointer la o dimensiune a alocat tampon ca un al doilea argument și flux ca un al treilea argument. (va în esență loc stdin pentru linia de comandă de intrare). Și returnează numărul de caractere citit, inclusiv \n la final, dar nu nul de încheiere.

Aici, puteți vedea exemplu de utilizare a acestei funcții:

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;
}

Desigur, folosind această funcție necesită cunoștințe de bază despre indicii și dinamică de memorie în C, însă puțin mai complicat natura getline este cu siguranță în valoare de ea, pentru că a furnizat securitate și flexibilitate.

Puteți citi mai multe despre această funcție, și alte funcții de intrare disponibile în C, de pe acest site: https://www.studymite.com/blog/strings-in-c Cred că rezumă complexitatea C intrare destul de bine.

2021-11-23 19:18:00

Multumesc pentru sfaturi si link-ul, mă ajută foarte mult.
Becker
1

Dacă aveți de exemplu un personaj matrice a declarat ca

char s[100];

și vreau să citesc un string care conține spații încorporate apoi, puteți utiliza fie scanf felul următor:

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

sau fgets cum ar fi:

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

Diferența dintre aceste două apeluri este că apelul de scanf nu citește caracterul linie noua '\n' din buffer de intrare. În timp ce fgets citește caracterul linie noua '\n' dacă nu există suficient spațiu în caracterul matrice.

Pentru a elimina caracterul linie noua '\n' care este stocat în caracterul matrice după utilizarea fgets puteți scrie, de exemplu:

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

Dacă șirul de intrare are mai mult de 99 de caractere atunci ambele apeluri citit doar 99 de personaje și adăugați-o secvență de caractere cu încheiere caracter zero '\0'. Toate celelalte caractere vor fi încă în tampon de intrare.

Există o problemă cu fgets. De exemplu, dacă înainte de a fgets nu este folosit scanf ca de exemplu:

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

și datele introduse de utilizator este:

10
Hello World

apoi apelul de fgets va citi numai caracterul linie noua '\n' care este stocat in buffer după apăsarea tastei Enter atunci când valoarea întreagă în cererea de scanf a fost citit.

În acest caz, aveți nevoie pentru a scrie un cod care va elimina caracterul linie noua '\n' înainte de a apela fgets.

Puteți face acest lucru, de exemplu, felul următor:

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

Dacă utilizați scanf apoi, în astfel de situație se poate scrie:

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

În alte limbi

Această pagină este în alte limbi

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