Wady scanf
Chcę poznać wady scanf()
.
W wielu miejscach czytałem, że użycie scanf
może powodować przepełnienie bufora. Jaki jest tego powód? Czy są jakieś inne wady scanf
?
9 answers
Problemy ze scanf są (co najmniej):
- użycie
%s
, aby uzyskać łańcuch znaków od użytkownika, co prowadzi do możliwości, że łańcuch może być dłuższy niż twój bufor, powodując przepełnienie. - możliwość nieudanego skanowania pozostawiając wskaźnik pliku w nieokreślonej lokalizacji.
Bardzo wolę używać fgets
do odczytu całych linii, aby ograniczyć ilość odczytywanych danych. Jeśli masz bufor 1K i wczytujesz do niego linijkę fgets
to może stwierdzić, czy linia była zbyt długa przez fakt, że nie ma znaku kończącego znak nowej linii (niezależnie od ostatniej linii pliku bez nowej linii).
Następnie możesz złożyć skargę do użytkownika lub przydzielić więcej miejsca na resztę linii (w razie potrzeby stale, dopóki nie masz wystarczająco dużo miejsca). W obu przypadkach nie ma ryzyka przepełnienia bufora.
Po przeczytaniu linii, wiesz że jesteś ustawiony w następnej linii, więc nie ma problemu. Możesz wtedy sscanf
ciąg do zawartości serca bez konieczności zapisywania i przywracania wskaźnika pliku do ponownego odczytu.
Oto fragment kodu, którego często używam, aby upewnić się, że nie przepełnia się bufor pytając użytkownika o informacje.
Można go łatwo dostosować, aby użyć pliku innego niż standardowe wejście, jeśli to konieczne, a także można go przypisać własny bufor (i zwiększać go, dopóki nie będzie wystarczająco duży) przed oddaniem go z powrotem do wywołującego (chociaż wywołujący byłby wtedy odpowiedzialny za jego uwolnienie, oczywiście).
#include <stdio.h>
#include <string.h>
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
#define SMALL_BUFF 3
static int getLine (char *prmpt, char *buff, size_t sz) {
int ch, extra;
// Size zero or one cannot store enough, so don't even
// try - we need space for at least newline and terminator.
if (sz < 2)
return SMALL_BUFF;
// Output prompt.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
// Get line with buffer overrun protection.
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
size_t lastPos = strlen(buff) - 1;
if (buff[lastPos] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[lastPos] = '\0';
return OK;
}
I, kierowca testowy dla niego:
// Test program for getLine().
int main (void) {
int rc;
char buff[10];
rc = getLine ("Enter string> ", buff, sizeof(buff));
if (rc == NO_INPUT) {
// Extra NL since my system doesn't output that on EOF.
printf ("\nNo input\n");
return 1;
}
if (rc == TOO_LONG) {
printf ("Input too long [%s]\n", buff);
return 1;
}
printf ("OK [%s]\n", buff);
return 0;
}
W końcu test pokazujący go w akcji:
$ ./tstprg
Enter string>[CTRL-D]
No input
$ ./tstprg
Enter string> a
OK [a]
$ ./tstprg
Enter string> hello
OK [hello]
$ ./tstprg
Enter string> hello there
Input too long [hello the]
$ ./tstprg
Enter string> i am pax
OK [i am pax]
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-12 01:08:13
Większość odpowiedzi do tej pory wydaje się skupiać na problemie przepełnienia bufora łańcuchów. W rzeczywistości, specyfikatory formatów, które mogą być używane z funkcjami scanf
, obsługują jawne ustawienie szerokości pola , które ogranicza maksymalny rozmiar wejścia i zapobiega przepełnieniu bufora. To sprawia, że popularne oskarżenia o przepełnienie bufora łańcuchów obecne w scanf
są praktycznie bezpodstawne. Twierdzenie, że {[0] } jest w jakiś sposób analogiczne do gets
pod tym względem jest całkowicie błędne. Jest major różnica jakościowa między scanf
a gets
: scanf
nie jest to jednak możliwe, ponieważ nie jest to możliwe.]}
Można argumentować, że te scanf
funkcje są trudne w użyciu, ponieważ szerokość pola musi być osadzona w łańcuchu formatu (nie ma sposobu, aby przekazać go przez zmienny argument, jak to można zrobić w printf
). To prawda. {[0] } jest rzeczywiście dość słabo zaprojektowany pod tym względem. Niemniej jednak wszelkie twierdzenia, że {[0] } jest jakoś beznadziejnie złamane w odniesieniu do bezpieczeństwa string-buffer-overflow są całkowicie fałszywe i zwykle wykonane przez leniwych programistów.
Prawdziwy problem z scanf
ma zupełnie inną naturę, choć dotyczy również przepełnienia. Gdy funkcja scanf
jest używana do konwersji dziesiętnych reprezentacji liczb na wartości typów arytmetycznych, nie zapewnia ochrony przed przepełnieniem arytmetycznym. Jeśli wystąpi przepełnienie, scanf
wytworzy nieokreślone zachowanie. Z tego powodu jedynym poprawnym sposobem wykonania konwersji w bibliotece standardowej C są funkcje z rodziny strto...
.
Tak więc, podsumowując powyższe, problem z scanf
polega na tym, że jest to trudne (aczkolwiek możliwe) do prawidłowego i bezpiecznego użycia z buforami łańcuchowymi. I nie można bezpiecznie używać do wprowadzania arytmetycznego. Ten ostatni jest prawdziwym problemem. Ta pierwsza jest tylko niedogodnością.
P. S. powyższe ma dotyczyć całej rodziny scanf
funkcji (w tym również fscanf
i sscanf
). W przypadku scanf
oczywistym problemem jest to, że sam pomysł użycia ściśle sformatowanej funkcji do odczytu potencjalnie interaktywnego danych wejściowych jest raczej wątpliwy.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-29 14:39:46
Z komp.lang.C FAQ: dlaczego wszyscy mówią, aby nie używać scanf? Czego powinienem użyć zamiast tego?
scanf
ma wiele problemów-zobacz pytania 12.17, 12.18 a , oraz 12.19. Również jego format%s
ma ten sam problem cogets()
(Zobacz pytanie 12.23)-trudno jest zagwarantować, że bufor odbiorczy nie przepełni się. [przypis]Bardziej ogólnie, {[0] } jest przeznaczony do stosunkowo structured, formatted input (jego nazwa w rzeczywistości pochodzi od "scan formatted"). Jeśli zwrócisz uwagę, powie Ci, czy się udało, czy nie, ale może powiedzieć tylko w przybliżeniu, gdzie się nie powiodło, a wcale nie jak i dlaczego. Masz bardzo mało możliwości odzyskania błędów.
Interaktywne wejście użytkownika jest jednak najmniej ustrukturyzowanym wejściem. Dobrze zaprojektowany interfejs użytkownika pozwoli na możliwość pisania przez użytkownika niemal wszystkiego-nie tylko liter czy interpunkcja, gdy oczekiwano cyfr, ale także więcej lub mniej znaków niż oczekiwano, lub w ogóle żadnych znaków ( tj., tylko klawisz RETURN), lub przedwczesne EOF, lub cokolwiek innego. Jest prawie niemożliwe, aby z gracją poradzić sobie z tymi wszystkimi potencjalnymi problemami podczas używania
scanf
; o wiele łatwiej jest odczytać całe wiersze( zfgets
lub tym podobne), a następnie zinterpretować je, używającsscanf
lub innych technik. (Funkcje takie jakstrtol
,strtok
, iatoi
są często przydatne; Zobacz też pytania 12.16 oraz 13.6.) Jeśli używasz dowolnego wariantuscanf
, sprawdź wartość zwracaną, aby upewnić się, że znaleziono oczekiwaną liczbę elementów. Ponadto, jeśli używasz%s
, pamiętaj, aby chronić przed przepełnieniem bufora.Zauważ przy okazji, że krytyka
scanf
niekoniecznie jest oskarżeniemfscanf
isscanf
.scanf
czyta zstdin
, który jest zwykle interaktywną klawiaturą i dlatego jest najmniej ograniczony, co prowadzi do większości problemów. Gdy plik danych ma znany format, z drugiej strony może być właściwe odczytanie go za pomocąfscanf
. Jest całkowicie właściwe parsowanie łańcuchów za pomocąsscanf
(o ile zwracana wartość jest zaznaczona), ponieważ tak łatwo jest odzyskać kontrolę, ponownie uruchomić skanowanie, odrzucić dane wejściowe, jeśli nie pasują, itd.Dodatkowe linki:
[[19]}Bibliografia: K & R2 sek. 7.4 str. 159
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-05 13:50:28
Tak, masz rację. W rodzinie scanf
istnieje poważna wada bezpieczeństwa(scanf
,sscanf
, fscanf
..itd) esp podczas czytania łańcucha, ponieważ nie biorą pod uwagę długości bufora (do którego są odczytywane).
Przykład:
char buf[3];
sscanf("abcdef","%s",buf);
Oczywiście bufor buf
może pomieścić znak max 3
. Ale sscanf
spróbuje umieścić "abcdef"
w nim powodując przepełnienie bufora.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-12 03:25:55
Bardzo trudno jest zrobić to, co chcesz. Oczywiście, że możesz, ale rzeczy takie jak scanf("%s", buf);
są równie niebezpieczne jak gets(buf);
, Jak wszyscy mówili.
Jako przykład, co paxdiablo robi w swojej funkcji do czytania, można zrobić za pomocą czegoś takiego:
scanf("%10[^\n]%*[^\n]", buf));
getchar();
Powyższe spowoduje odczytanie linii, zapisanie pierwszych 10 znaków nie-nowej linii w buf
, a następnie odrzucenie wszystkiego do (włącznie) nowej linii. Zatem funkcję paxdiablo można zapisać używając scanf
następującego sposób:
#include <stdio.h>
enum read_status {
OK,
NO_INPUT,
TOO_LONG
};
static int get_line(const char *prompt, char *buf, size_t sz)
{
char fmt[40];
int i;
int nscanned;
printf("%s", prompt);
fflush(stdout);
sprintf(fmt, "%%%zu[^\n]%%*[^\n]%%n", sz-1);
/* read at most sz-1 characters on, discarding the rest */
i = scanf(fmt, buf, &nscanned);
if (i > 0) {
getchar();
if (nscanned >= sz) {
return TOO_LONG;
} else {
return OK;
}
} else {
return NO_INPUT;
}
}
int main(void)
{
char buf[10+1];
int rc;
while ((rc = get_line("Enter string> ", buf, sizeof buf)) != NO_INPUT) {
if (rc == TOO_LONG) {
printf("Input too long: ");
}
printf("->%s<-\n", buf);
}
return 0;
}
Jednym z innych problemów z scanf
jest jego zachowanie w przypadku przepełnienia. Na przykład, podczas czytania int
:
int i;
scanf("%d", &i);
Powyższego nie można bezpiecznie używać w przypadku przepełnienia. Nawet w pierwszym przypadku czytanie ciągu znaków jest o wiele prostsze do zrobienia z fgets
, a nie z scanf
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-12 06:39:42
Problemy, które mam z *scanf()
rodziną:
- możliwość przepełnienia bufora ze specyfikatorami % s i % [konwersji. Tak, możesz określić maksymalną szerokość pola, ale w przeciwieństwie do
printf()
, nie możesz uczynić go argumentem w wywołaniuscanf()
; musi być zakodowany na twardo w specyfikatorze konwersji. - potencjał przepełnienia arytmetycznego z %d, % i itd.
- ograniczona zdolność do wykrywania i odrzucania źle uformowanych danych wejściowych. Na przykład, "12w4" nie jest poprawną liczbą całkowitą, ale
scanf("%d", &value);
będzie pomyślnie przekonwertowano i przypisano 12 dovalue
, pozostawiając "w4" w strumieniu wejściowym, aby zablokować przyszły odczyt. W idealnym przypadku cały łańcuch wejściowy powinien zostać odrzucony, alescanf()
nie daje łatwego mechanizmu, aby to zrobić.
Jeśli wiesz, że Twoje dane wejściowe zawsze będą dobrze uformowane z ciągami o stałej długości i wartościami liczbowymi, które nie flirtują z przepełnieniem, to scanf()
jest świetnym narzędziem. Jeśli masz do czynienia z interaktywnym wejściem lub wejściem, które nie jest gwarantowane dobrze uformowany, a następnie użyj czegoś innego.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-12 15:18:24
Wiele odpowiedzi omawia potencjalne problemy z przepełnieniem podczas używania scanf("%s", buf)
, ale najnowsza Specyfikacja POSIX rozwiązuje ten problem, dostarczając znak m
assign-allocation, który może być używany w specyfikacjach formatów dla c
, s
, i [
formaty. Pozwala to scanf
przydzielić tyle pamięci, ile jest to konieczne za pomocą malloc
(więc musi zostać uwolniona później za pomocą free
).
Przykład jego użycia:
char *buf;
scanf("%ms", &buf); // with 'm', scanf expects a pointer to pointer to char.
// use buf
free(buf);
Zobacz tutaj . Wady takiego podejścia jest to stosunkowo nowy dodatek do specyfikacji POSIX i w ogóle nie jest określony w specyfikacji C, więc na razie pozostaje raczej nieortable.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-03 01:20:04
Jest jeden duży problem z scanf
-podobnymi funkcjami-brakiemjakiegokolwiek bezpieczeństwa typu. Oznacza to, że możesz to kodować:
int i;
scanf("%10s", &i);
Cholera, nawet to jest "w porządku":
scanf("%10s", i);
Jest gorzej niż printf
-podobne funkcje, ponieważ scanf
oczekuje wskaźnika, więc awarie są bardziej prawdopodobne.
Oczywiście, istnieją pewne Kontrolery określające format, ale te nie są idealne i dobrze, nie są częścią języka lub biblioteki standardowej.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-13 14:48:10
Zaletą scanf
jest to, że gdy nauczysz się używać tego narzędzia, jak zawsze powinieneś to robić w C, ma ono niezwykle użyteczne zastosowania. możesz dowiedzieć się, jak używać scanf
i przyjaciół, czytając i rozumiejąc podręcznik. Jeśli nie możesz przejść przez ten podręcznik bez poważnych problemów ze zrozumieniem, prawdopodobnie oznacza to, że nie znasz dobrze języka C.
Jedną z największych wad wydaje się być wyłącznie reputacja, którą zdobywa wśród niewtajemniczonych; podobnie jak w przypadku wielu przydatnych funkcji C powinniśmy być dobrze poinformowani, zanim go użyjemy. Kluczem jest uświadomienie sobie, że podobnie jak w przypadku reszty C, to wydaje się zwięzłe i idiomatyczne, ale to może być subtelnie mylące. Jest to wszechobecne w C; początkującym łatwo jest napisać kod, który ich zdaniem ma sens i może nawet zadziałać dla nich na początku, ale nie ma sensu i może zawieść katastrofalnie. Na przykład niewtajemniczeni zwykle oczekują, że delegat Jaka byłaby odpowiedź na to pytanie bez wspominania o braku bezpieczeństwa i ryzyku przepełnienia bufora? Jak już omówiliśmy, C nie jest bezpiecznym językiem i pozwoli nam na skrócenie czasu, ewentualnie zastosowanie optymalizacji kosztem poprawności lub bardziej prawdopodobne, ponieważ jesteśmy leniwymi programistami. Tak więc, gdy wiemy, że system nigdy nie otrzyma łańcucha większego niż stała liczba bajtów, otrzymujemy możliwość deklarowania tablicy o takim rozmiarze i sprawdzania granic. Nie postrzegam tego jako upadku w dół; jest to opcja. Ponownie, czytanie instrukcji jest zdecydowanie zalecane i ujawniłoby tę opcję dla nas. Leniwi programiści nie są jedynymi użądlonymi Niedoświadczeni programiści nie są zmuszeni do rozważenia sukcesu operacji . Załóżmy, że użytkownik wprowadzi coś całkowicie nie-numerycznego, gdy powiemy Kiedy przyjaciele nie przeczytają tego, co im kazano czytać, strumień zostanie pozostawiony w niezwykłym stanie.]}
- W przypadku Nie jest łatwiej używać Niestety, Lekka adaptacja, rozdzielając dwa delegaty formatu i widzimy tutaj pewien sukces: scanf
i przyjaciele cierpieli z powodu niefortunnych wyborów projektowych, które utrudniały (i czasami niemożliwe) do poprawnego użycia bez czytania dokumentacji, jak pokazały inne odpowiedzi. Niestety, występuje to w całym C, więc jeśli miałbym odradzać używanie scanf
, to prawdopodobnie odradzałbym używanie C.]}
%s
spowoduje odczytanie linii i chociaż może to wydawać się intuicyjne, niekoniecznie jest to prawdą. Właściwsze jest opisanie pola odczytanego jako a word . Czytanie instrukcji jest zdecydowanie zalecane dla każdej funkcji.scanf
. Często zdarza się, że ludzie próbują odczytać float
lub double
wartości używając na przykład %d
. Zwykle mylą się w przekonaniu, że implementacja wykona jakąś konwersję za scen, co miałoby sens, ponieważ podobne konwersje zdarzają się w całej reszcie języka, ale tutaj tak nie jest. Jak powiedziałem wcześniej, scanf
i przyjaciele (i rzeczywiście reszta C) są zwodnicze; wydają się zwięzłe i idiomatyczne, ale nie są. scanf
, aby odczytał i przekonwertował ciąg cyfr dziesiętnych za pomocą %d
. Jedynym sposobem na przechwycenie takich błędnych danych jest sprawdzenie zwracanej wartości, a jak często zawracamy sobie głowę sprawdzaniem zwracanej wartości?fgets
, jeśli nie ma wystarczającej ilości miejsca do przechowywania pełnego wiersza, reszta nieprzeczytanego wiersza może być błędnie traktowana tak, jakby była to nowa linia, Jeśli nie jest.
- W przypadku scanf
i znajomych konwersja nie powiodła się, jak opisano powyżej, błędne dane pozostają nieprzeczytane w strumieniu i mogą być błędnie traktowane tak, jakby były częścią innego pola.scanf
i przyjaciół niż używać fgets
. Jeśli sprawdzamy sukces, szukając '\n'
, gdy używamy fgets
lub sprawdzając wartość zwracaną, gdy używamy scanf
i przyjaciół, i okaże się, że przeczytaliśmy niekompletną linię używając fgets
lub nie przeczytaliśmy jej pole używając scanf
, wtedy mamy do czynienia z tą samą rzeczywistością: prawdopodobnie odrzucimy dane wejściowe (Zwykle do następnej linii nowej linii włącznie)! Yuuuuuuuu!scanf
jedno i drugie sprawia, że odrzucenie danych wejściowych w ten sposób jest trudne (nieintuicyjne) i łatwe (mało naciśnięć klawiszy). W obliczu tej rzeczywistości odrzucania wkładu użytkownika, niektórzy próbowali , nie zdając sobie sprawy, że delegat scanf("%*[^\n]%*c");
%*[^\n]
zawiedzie, gdy napotka tylko nową linię, a więc nowa linia nadal pozostanie w strumieniu.scanf("%*[^\n]"); getchar();
. Spróbuj to zrobić z tak małą liczbą naciśnięć klawiszy za pomocą innego narzędzia;) [32]}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-11 22:28:05