Mal by som používať fgets alebo scanf s obmedzeným vstupom do c?

0

Otázka

Mal by som používať fgets alebo formátovaný scanf ako scanf("%10s", foo).

S výnimkou, že scanf nie prečítať prázdne znaky, ktorý môže byť vyriešený, a to viac vách s scanset, potom prečo by som mal použiť fgets namiesto scanf?

Akúkoľvek pomoc, chcel by som byť ocenil.


Upraviť

Jedna vec, ktorú chcem sa opýtať je: aj keď používame fgets čo sa stane, ak používateľ zadať viac znakov, než je hranica (mám na mysli veľa znakov), to viesť k pretečenie medzipamäte? Potom, ako sa s ňou vyrovnať?

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

Najlepšiu odpoveď

5

Na väčšine operačných sytems, vstup užívateľa je na základe predvoleného nastavenia, line-založené. Jeden dôvod pre toto je umožniť používateľovi stlačením tlačidla backspace správne vstupné, pred odoslaním vstup do programu.

Pre linka na báze vstup užívateľa, je zmysluplné a intuitívne pre program na čítanie jeden riadok vstupu v čase. To je to, čo funkcia fgets sa (za predpokladu, že buffer je dostatočne veľký na ukladanie celý riadok vstupu).

Funkcia scanfna druhej strane, bežne nečíta jeden riadok vstupu v čase. Napríklad, keď používate %s alebo %d formát konverzie špecifikátor s scanfnebude konzumovať celý riadok vstupu. Namiesto toho, ho bude konzumovať iba toľko textu, ako zodpovedá formát konverzie špecifikátor. To znamená, že newline znaku na koniec riadku normálne nemá byť konzumované (ktorý môže ľahko viesť k programové chyby). Tiež, scanf s názvom %d formát konverzie špecifikátor bude zvažovať vstup ako 6sldf23dsfh2 ako platné textu pre číslo 6, ale žiadne ďalšie hovory na scanf s rovnakým špecifikátor zlyhá, pokiaľ ste sa zbavili zvyšok riadku, z vstupný stream.

Toto správanie scanf je prekvapujúcich, keďže správanie fgets je intuitívne, pri jednaní s line-založené vstupu používateľa.

Po použití fgets,, môžete použiť funkciu sscanf na provázku, pri rozbore obsahu jednotlivých riadkoch. Umožní vám pokračovať v používaní scansets. Alebo môžete analyzovať line iným spôsobom. Buď ako buď, ako dlho, ako ste pomocou fgets namiesto scanf pre čítanie textu, budete manipulácia jeden riadok vstupu v čase, ktorý je prirodzený a intuitívny spôsob, ako sa vysporiadať s line-založené vstupu používateľa.

Keď používame fgets čo sa stane, ak používateľ zadať viac znakov, než je hranica (mám na mysli veľa znakov), to viesť k pretečenie medzipamäte? Potom, ako sa s ňou vyrovnať?

Ak užívateľ zadá viac znakov, než sa zmestí do buffra, ako je uvedené v druhom fgets argumentom funkcie, potom to nebude pretečenie medzipamäte. Namiesto toho to bude iba výpis toľko znakov od vstupného prúdu ako nosenie v buffri. Môžete určiť, či celý riadok bol čítať overenie, či je reťazec, ktorý obsahuje nový riadok znakov '\n' na konci.

2021-11-23 15:13:39

Ďakujem, vaša odpoveď naozaj užitočné pre mňa.
Becker

Správanie fgets() je iba intuitívne za vstupy, ktoré nie sú dlhšie, než sa očakávalo.
supercat
2

Toto je najčastejšie diskutovanou témou, plný názor, ale zaujímavé žiadny menej. Som pozoroval, že veľká väčšina z tých, ktoré už odpovedal na podobné otázky, na tejto stránke pokles na strane fgets(). Ja som jeden z nich. Zistil som, fgets() byť oveľa lepšie použiť na vstup používateľa, ako scanf() s niekoľkými výnimkami. scanf() je zvažoval veľa ako sub-optimálne metódy pre manipuláciu vstupu používateľa. Napríklad

"...to vám povie, či je to podarilo alebo nepodarilo, ale môžu vám povedať len približne kde sa to nepodarilo, a nie na všetky, ako a prečo. Ste majú veľmi malú možnosť urobiť žiadnu chybu obnovy."
(jamesdlin). Ale v záujme pokus rovnováhu, začať citovať túto diskusiu.

Pre vstup užívateľa, ktorý pochádza z stdin, t. j. vstup z klávesnice, fgets() bude lepšou voľbou. To je oveľa viac priestoru v tom, že reťazec je číta môže byť plne potvrdené pred konverzie je pokus

Jeden z mála okamihov, na formulári scanf(): fscanf() bude v poriadku použitie môže byť pri prevode textu z veľmi riadený zdroj, t. j. z čítania prísne formátovaný súbor s opakovaním predvídateľné polia.

Pre ďalšie diskusie, toto porovnanie dvoch vrcholov ďalšie výhody a nevýhody oboch.

Edit: na adresu OP ďalšie otázky o pretečeniu:

"Jedna vec, ktorú chcem sa opýtať je: aj keď používame fgets čo sa stane, ak používateľ zadať viac znakov, než je hranica (mám na mysli veľa znaky), to viesť k pretečenie medzipamäte? Potom, ako sa vysporiadať s to?"

[fgets()](https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm je pekne navrhnutý tak, aby sa zabránilo buffer pretečeniu, jednoducho pomocou jeho parametre správne, napr.:

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

To zabraňuje vstupu väčšia ako veľkosť buffra zo spracovania, a tým predchádzať pretečeniu.

dokonca aj použitie scanf()prevencia pretečenie medzipamäte je docela rovno vpred: Použitie šírka špecifikátor v formátovací reťazec. Ak si chcete prečítať vstup pre príklad a obmedziť vstup veľkosť od užívateľa do 100 znakov max., kód bude obsahovať nasledujúce:

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

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

Avšak s číslami, pretečenie nie je tak pekné pomocou scanf(). Preukázať, použite tento jednoduchý kód, zadanie dve hodnoty uvedené v jeden komentár na spustenie:

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

Pre druhú hodnotu, môj systém hodí takto:

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

Tu je potrebné, aby si v reťazec, potom postupujte podľa reťazec na číslo konverzie pomocou jedného z strto_() funkcie: strtol(), strtod(), ...). Obe zahŕňajú schopnosť test pre pretečeniu pred spôsobuje run-time varovanie alebo chyba. Všimnite si, že pomocou atoi(), atod() nebude chrániť pred prepadom buď.

2021-11-23 14:10:20

Ďakujem, som naozaj oceňujem vašu odpoveď.
Becker

Na otázku "plný názoru"? Musím vrelo súhlasiť. To nie je vec názoru, že scanf je takmer úplne k ničomu, dobré na to najlepšie pre čítanie jedného, jednoduché vstupy v intro-do-C programov, ale veľmi ťažké urobiť niečo vzdialene sofistikované s — tieto sú zrejmé fakty! :-)
Steve Summit
1

Tak ďaleko, všetky odpovede tu prezentované zložitosti scanf a fgets,, ale to, čo verím, je potrebné spomenúť, je, že obe tieto funkcie sú dlhšie v súčasnej C štandardu. Scanf je obzvlášť nebezpečné, pretože má všetky druhy bezpečnostných problémov s buffer prietokov. fgets nie je tak problematické, ale z mojej skúsenosti, má tendenciu byť trochu neohrabaný a nie-tak-užitočné v praxi.

Pravdou je, že často si naozaj neviem, ako dlho vstup užívateľa bude. Môžete obísť použitím fgets s dúfam, že to bude dosť veľké rezervy, ale to naozaj nie je ellegant. Namiesto toho, čo sa vám často, že chcete urobiť, je mať dynamický rezervy, ktoré bude rásť a byť dostatočne veľká, aby store čokoľvek vstup užívateľa prinesie. A to je, keď getline funkcia prichádza do hry. Používa sa na čítanie ľubovoľný počet znakov od užívateľa, kým \n je stretli. V podstate, to načíta celý riadok, na svoju pamäť, ako string.

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

Túto funkciu berie ukazovateľ na dynamicky alokovaný reťazec ako prvý argument, a ukazovateľ na veľkosť prideleného buffer ako druhý argument a prúd ako tretí argument. (budete v podstate miesto stdin tam na príkaz-line input). A vráti počet čítať znaky, vrátane \n na konci, ale nie na ukončenie null.

Tu môžete vidieť príklady použitia tejto funkcie:

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

Samozrejme, pomocou tejto funkcie sa vyžaduje základné vedomosti o ukazovatele a dynamické pamäte C, avšak o niečo zložitejšie charakter getline rozhodne stojí za to, pretože za predpokladu, bezpečnosti a flexibility.

Môžete si prečítať viac o tejto funkcie, a iné vstupné funkcie, ktoré sú dostupné v C, na tejto stránke: https://www.studymite.com/blog/strings-in-c Verím, že to sumarizuje zložitosti C príkon celkom dobre.

2021-11-23 19:18:00

Vďaka za vaše rady a odkaz, že mi pomáha veľa.
Becker
1

Ak máte napríklad znak pole deklarované ako

char s[100];

a chcete čítať reťazec, ktorý obsahuje vložené medzery potom môžete použiť buď scanf nasledujúcim spôsobom:

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

alebo fgets ako:

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

Rozdiel medzi týmito dvoma hovormi je, že volanie scanf nie prečítať nový riadok znakov '\n' zo vstupného buffera. Zatiaľ čo fgets číta nový riadok znakov '\n' ak nie je dostatok miesta v znakové pole.

Ak chcete odstrániť nový riadok znakov '\n' ktorý je uložený v character array po použití fgets môžete napísať napríklad:

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

Ak vstupný reťazec má viac ako 99 znaky, potom sa oba hovory len na čítanie 99 znakov a append postupnosť znakov, s ktorým sa ukončuje nulový znak '\0'. Všetky ostatné znaky budú stále vo vstupnom bufferi.

Tam je problém s fgets. Napríklad, ak pred fgets tam sa používa scanf ako napríklad:

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

a používateľský vstup:

10
Hello World

potom hovor z fgets bude čítať iba na nový riadok znakov '\n' ktorý je uložený v buffri po stlačení klávesu Enter, keď celočíselné hodnoty v rámci výzvy scanf bol prečítaný.

V tomto prípade budete musieť napísať kód, ktorý odstráni nový riadok znakov '\n' pred zavolaním fgets.

Môžete to urobiť napríklad takto:

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

Ak používate scanf potom v takejto situácii môžete napísať:

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

V iných jazykoch

Táto stránka je v iných jazykoch

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