Których funkcji z biblioteki standardowej należy (należy) unikać?


90

Czytałem na Stack Overflow, że niektóre funkcje C są „przestarzałe” lub „należy ich unikać”. Czy możesz mi podać kilka przykładów tego rodzaju funkcji i dlaczego?

Jakie istnieją alternatywy dla tych funkcji?

Czy możemy z nich bezpiecznie korzystać - jakieś dobre praktyki?


3
Uważam, że dobrze jest wiedzieć. Niektórych funkcji z C należy naprawdę unikać i używać ich tylko do celów edukacyjnych.
INS

@Felix, byłoby łatwiej edytować jedną pojedynczą (oznaczoną jako poprawna) odpowiedź w przyszłości. To naprawdę unosi się w powietrzu, jeśli chodzi o odpowiedzi, które prawdopodobnie będą się zmieniać w czasie. Kto wie, może Jeff dostarczy odznakę „woźnego” ludziom, którzy będą aktualizować odpowiedzi w ciągu najbliższych kilku lat.
Tim Post,

1
@Tim Post: Ok, usunę swoje komentarze.
Felix Kling

3
Nie użyłbym go strncpy()jako ogólnego zamiennika strcpy()i nie użyłbym strncat()nigdy, ponieważ ma najbardziej nieintuicyjny interfejs, jaki można sobie wyobrazić - czy WIESZ, co określa parametr długości?
Jonathan Leffler

2
Używanie strncpy i strncat jest prawie zawsze błędem. Z pewnością nie powinny być używane zamiast strcpy i strcat !! scanf i sprintf są również doskonale użyteczne, jeśli wiesz, jak ich używać ...
R .. GitHub STOP HELPING ICE

Odpowiedzi:


58

Niezabezpieczone funkcje
przestarzałe
Doskonałym przykładem takiej funkcji jest gets () , ponieważ nie ma sposobu, aby określić, jak duży jest bufor docelowy. W rezultacie każdy program, który odczytuje dane wejściowe za pomocą funkcji gets (), ma lukę przepełnienia buforu . Z podobnych powodów należy użyć strncpy () zamiast strcpy () i strncat () zamiast strcat () .

Jeszcze więcej przykładów obejmuje funkcje tmpfile () i mktemp () ze względu na potencjalne problemy z bezpieczeństwem przy nadpisywaniu plików tymczasowych, które są zastępowane przez bezpieczniejszą funkcję mkstemp () .

Non-Reentrant
Inne przykłady to gethostbyaddr () i gethostbyname (), które nie są ponownie wchodzące (i dlatego nie są gwarantowane, że są bezpieczne dla wątków) i zostały zastąpione przez ponownie wprowadzające getaddrinfo () i freeaddrinfo () .

Możesz zauważyć tutaj wzorzec ... albo brak bezpieczeństwa (być może z powodu braku wystarczającej ilości informacji w podpisie, aby prawdopodobnie zaimplementować go w bezpieczny sposób) lub brak ponownego wejścia są typowymi źródłami wycofania.

Nieaktualne,
nieprzenośne Niektóre inne funkcje stają się po prostu przestarzałe, ponieważ powielają funkcje i nie są tak przenośne, jak inne warianty. Na przykład bzero () jest przestarzałe na rzecz memset () .

Bezpieczeństwo nici i ponowne wejście
Zapytałeś w swoim poście o bezpieczeństwo nici i ponowne wejście . Jest niewielka różnica. Funkcja jest ponownie wprowadzana, jeśli nie używa żadnego wspólnego, zmiennego stanu. Na przykład, jeśli wszystkie potrzebne informacje są przekazywane do funkcji, a wszelkie potrzebne bufory są również przekazywane do funkcji (zamiast współdzielonych przez wszystkie wywołania funkcji), to jest ona ponownie wprowadzana. Oznacza to, że różne wątki, używając niezależnych parametrów, nie ryzykują przypadkowego udostępnienia stanu. Reentrancy to silniejsza gwarancja niż bezpieczeństwo nici. Funkcja jest bezpieczna wątkowo, jeśli może być używana przez wiele wątków jednocześnie. Funkcja jest bezpieczna wątkowo, jeśli:

  • Jest ponownie wprowadzany (tj. Nie dzieli żadnego stanu między wywołaniami) lub:
  • Nie jest ponownie wprowadzany, ale używa synchronizacji / blokowania w razie potrzeby dla stanu udostępnionego.

Ogólnie rzecz biorąc, w specyfikacji Single UNIX i IEEE 1003.1 (tj. „POSIX”) żadna funkcja, dla której nie ma gwarancji, że zostanie ponownie wprowadzona, nie jest gwarantowana jako bezpieczna wątkowo . Innymi słowy, w aplikacjach wielowątkowych można używać przenośnie tylko funkcji, które mają zagwarantowaną możliwość ponownego wejścia (bez zewnętrznego blokowania). Nie oznacza to jednak, że implementacje tych standardów nie mogą wybrać funkcji zabezpieczającej wątki bez ponownego wchodzenia. Na przykład Linux często dodaje synchronizację do funkcji niewymagających ponownego wchodzenia, aby dodać gwarancję (poza specyfikacją pojedynczej specyfikacji UNIX) bezpieczeństwa wątków.

Ciągi
znaków (i ogólnie bufory pamięci) Zapytałeś również, czy jest jakaś podstawowa wada w łańcuchach / tablicach. Niektórzy mogą twierdzić, że tak jest, ale ja argumentowałbym, że nie, nie ma fundamentalnej wady języka. C i C ++ wymagają osobnego przekazania długości / pojemności tablicy (nie jest to właściwość „.length”, jak w niektórych innych językach). To nie jest wada sama w sobie. Każdy programista C i C ++ może napisać poprawny kod, po prostu przekazując długość jako parametr w razie potrzeby. Problem polega na tym, że kilka interfejsów API, które wymagały tych informacji, nie określiło ich jako parametru. Lub załóżmy, że zostanie użyta pewna stała MAX_BUFFER_SIZE. Takie interfejsy API zostały obecnie wycofane i zastąpione przez alternatywne interfejsy API, które umożliwiają określenie rozmiarów tablic / buforów / ciągów.

Scanf (w odpowiedzi na twoje ostatnie pytanie)
Osobiście korzystam z biblioteki iostreams C ++ (std :: cin, std :: cout, operatory << i >>, std :: getline, std :: istringstream, std :: ostringstream itp.), więc zazwyczaj się tym nie zajmuję. Gdybym był zmuszony używać czystego C, osobiście po prostu użyłbym fgetc () lub getchar () w połączeniu ze strtol () , strtoul () itp. I przeanalizował wszystko ręcznie, ponieważ nie jestem wielkim fanem varargs lub ciągi formatujące. To powiedziawszy, zgodnie z moją najlepszą wiedzą, nie ma problemu z [f] scanf () , [f] printf ()itd., o ile sam tworzysz łańcuchy formatujące, nigdy nie przekazujesz dowolnych łańcuchów formatujących ani nie zezwalasz na użycie danych wejściowych użytkownika jako łańcuchów formatujących, i używasz makr formatujących zdefiniowanych w <inttypes.h> tam, gdzie to konieczne. (Uwaga, snprintf () powinno być używane zamiast sprintf () , ale ma to związek z niepowodzeniem określenia rozmiaru bufora docelowego, a nie użyciem ciągów formatujących). Powinienem również zauważyć , że w C ++ boost :: format zapewnia formatowanie podobne do printf bez varargs.


4
„Przestarzały” to mocne słowo, które ma określone znaczenie podczas omawiania standardu C ++. W tym sensie metody gets (), strcpy () itp. Nie są przestarzałe.

4
Tylko pod warunkiem, że rozróżnisz między „wycofane przez standard C”, „zdeprecjonowane przez Michaela Aarona Safyana” i „wycofane przez nieznaną osobę lub osoby, które mają nadzieję, że wiedzą, o czym mówią [potrzebne źródło]”. Pytanie jest o stylu korzystne kodowania, a nie o standardzie C, więc pozostałe dwie są odpowiednie. Ale tak jak Neil, wymagałem podwójnej refleksji, zanim zdałem sobie sprawę, że twoje stwierdzenia nie miały na celu implikacji pierwszego znaczenia.
Steve Jessop

11
strncpygeneralnie należy również unikać. Nie robi tego, co zakłada większość programistów. Nie gwarantuje zakończenia (co prowadzi do przepełnienia bufora) i wypełnia krótsze ciągi (w niektórych przypadkach może obniżać wydajność).
Adrian McCarthy

2
@Adrian: Zgadzam się z tobą - ani n-less strncpy()nie strncat()jest sensownym zamiennikiem ani tym gorzej .
Jonathan Leffler

4
Ta odpowiedź szerzy bezsensowny dogmat dotyczący „zastąp strcpy strncpy - nie wiem dlaczego, ale Microsoft mi to nakazuje”. strncpy nigdy nie miał być bezpieczną wersją strcpy! W rzeczywistości jest to o wiele bardziej niebezpieczne. Zobacz Dlaczego strlcpy i strlcat są uważane za niebezpieczne? .
Lundin

24

Po raz kolejny ludzie powtarzają, przypominając mantrę, absurdalne stwierdzenie, że wersja „n” funkcji str jest wersją bezpieczną.

Gdyby to było to, do czego były przeznaczone, zawsze kończyłyby ciągi zerowe.

Wersje "n" funkcji zostały napisane do użytku z polami o stałej długości (takimi jak wpisy katalogów we wczesnych systemach plików), w których terminator nul jest wymagany tylko wtedy, gdy łańcuch nie wypełnia pola. Jest to również powód, dla którego funkcje mają dziwne efekty uboczne, które są bezcelowo nieefektywne, jeśli są używane tylko jako zamienniki - na przykład strncpy ():

Jeśli tablica wskazywana przez s2 jest łańcuchem krótszym niż n bajtów, bajty zerowe są dołączane do kopii w tablicy wskazywanej przez s1, aż do zapisania wszystkich n bajtów.

Ponieważ bufory przydzielane do obsługi nazw plików mają zwykle 4 KB, może to prowadzić do ogromnego pogorszenia wydajności.

Jeśli chcesz "rzekomo" bezpiecznych wersji, zdobądź - lub napisz własne - procedury strl (strlcpy, strlcat itp.), Które zawsze kończą łańcuchy nul i nie mają skutków ubocznych. Należy jednak pamiętać, że nie są one naprawdę bezpieczne, ponieważ mogą po cichu skrócić ciąg - rzadko jest to najlepszy sposób działania w jakimkolwiek programie w świecie rzeczywistym. Są sytuacje, w których jest to w porządku, ale istnieje również wiele okoliczności, w których może to prowadzić do katastrofalnych rezultatów (np. Drukowanie recept lekarskich).


1
Masz rację strncpy(), ale się mylisz strncat(). strncat()nie został zaprojektowany do użytku z polami o stałej długości - w rzeczywistości został zaprojektowany tak, aby strcat()ograniczał liczbę łączonych znaków. Dość łatwo jest używać tego jako „bezpiecznego strcat()”, śledząc miejsce pozostające w buforze podczas wykonywania wielu konkatenacji, a nawet łatwiej jest używać go jako „bezpiecznego strcpy()” (ustawiając pierwszy znak bufora docelowego na '\0'przed nazywając to). strncat() zawsze kończy ciąg docelowy i nie zapisuje dodatkowych znaków '\0'.
kawiarnia

2
@caf - tak, ale strncat () jest całkowicie bezużyteczna, ponieważ jako parametr przyjmuje maksymalną długość do skopiowania, a nie długość bufora docelowego. Aby zapobiec przepełnieniu buforu, musisz znać długość bieżącego ciągu docelowego, a jeśli wiesz, dlaczego miałbyś używać strncat () - która musi ponownie obliczyć długość dest - zamiast po prostu strlcat (), ciąg źródłowy do koniec ciągu dest.
Dipstick

To nadal nie zmienia faktu, że sugerujesz, że strncat()miejsce docelowe nie zawsze kończy się wartością nul i że zostało zaprojektowane do użytku z polami o stałej długości, z których oba są nieprawidłowe.
kawiarnia

2
@chrisharris: strncat()będzie działać poprawnie niezależnie od długości ciągu źródłowego, a strcat()nie. Problem strlcat()polega na tym, że nie jest to standardowa funkcja C.
David Thornley

@caf - masz rację co do strncat (). Właściwie nigdy go nie używałem, ponieważ - jak wskazałem powyżej - jest całkowicie bezużyteczny. Dlatego nadal należy go unikać.
Dipstick

19

Kilka odpowiedzi sugeruje użycie strncat()ponad strcat(); Sugerowałbym, że strncat()(i strncpy()) należy również unikać. Ma problemy, które utrudniają prawidłowe użytkowanie i prowadzą do błędów:

  • parametr długości do strncat()jest powiązany (ale niezupełnie - patrz punkt trzeci) z maksymalną liczbą znaków, które można skopiować do miejsca docelowego, a nie z rozmiarem buforu docelowego. To sprawia, że strncat()użycie jest trudniejsze niż powinno, zwłaszcza jeśli wiele elementów zostanie połączonych z miejscem docelowym.
  • może być trudne określenie, czy wynik został obcięty (co może być ważne lub nie)
  • łatwo o jeden błąd. Jak zauważa standard C99, „Tak więc maksymalna liczba znaków, które mogą znaleźć się w tablicy wskazywanej przez, s1wynosi strlen(s1)+n+1” dla wywołania, które wygląda jakstrncat( s1, s2, n)

strncpy()ma również problem, który może powodować błędy, które próbujesz używać w intuicyjny sposób - nie gwarantuje to, że miejsce docelowe zostanie zakończone z wartością null. Aby upewnić się, że musisz się upewnić, że specjalnie poradzisz sobie z tą narożną skrzynką, samodzielnie upuszczając '\0'w ostatniej lokalizacji bufora (przynajmniej w niektórych sytuacjach).

Sugerowałbym użycie czegoś takiego jak OpenBSD strlcat()i strlcpy()(chociaż wiem, że niektórzy ludzie nie lubią tych funkcji; uważam, że są znacznie łatwiejsze w użyciu niż strncat()/ strncpy()).

Oto trochę tego, co Todd Miller i Theo de Raadt mieli do powiedzenia na temat problemów z strncat()i strncpy():

Istnieje kilka problemów napotkanych podczas używania strncpy()i strncat()używania ich jako bezpiecznych wersji strcpy()i strcat(). Obie funkcje zajmują się zakończeniem NUL i parametrem długości na różne i nieintuicyjne sposoby, które dezorientują nawet doświadczonych programistów. Nie zapewniają również łatwego sposobu wykrywania, kiedy występuje obcięcie. ... Ze wszystkich tych problemów najważniejsze są zamieszanie spowodowane parametrami długości i związana z nimi kwestia zakończenia NUL. Kiedy przeprowadziliśmy audyt drzewa źródeł OpenBSD pod kątem potencjalnych luk w zabezpieczeniach, stwierdziliśmy powszechne nadużycia strncpy()i strncat(). Chociaż nie wszystkie z nich skutkowały lukami w zabezpieczeniach, które można było wykorzystać, dali jasno do zrozumienia, że ​​zasady używania strncpy()i strncat()bezpiecznych operacji na strunach są powszechnie źle rozumiane.

Audyt bezpieczeństwa OpenBSD wykazał, że błędy w tych funkcjach były „szerzące się”. W przeciwieństwie do gets()tych funkcji można bezpiecznie korzystać, ale w praktyce jest wiele problemów, ponieważ interfejs jest zagmatwany, nieintuicyjny i trudny w prawidłowym użyciu. Wiem, że Microsoft również przeprowadził analizę (chociaż nie wiem, ile swoich danych mógł opublikować), w wyniku czego zakazał (lub przynajmniej bardzo mocno odradzał - „zakaz” może nie być absolutny) użycie strncat()i strncpy()(między innymi).

Kilka linków z dodatkowymi informacjami:


1
O wiele lepiej jest śledzić długość strun poza samą struną. W ten sposób bezpieczne łączenie dwóch łańcuchów (-with-length) staje się kwestią prostego obliczenia (w celu uzyskania wymaganego rozmiaru bufora) możliwej ponownej alokacji, a memmove(). (Cóż, możesz użyć memcpy()w normalnym przypadku, gdy struny są niezależne.)
Donal Fellows

1
Twój drugi punkt jest całkowicie błędny - strncat() zawsze kończy ciąg docelowy.
kawiarnia

1
Twój drugi punkt jest błędny strncat(). Jednak jest to poprawne strncpy(), co ma inne problemy. strncat()jest rozsądnym zamiennikiem strcat(), ale strncpy()nie jest rozsądnym zamiennikiem strcpy().
David Thornley

2
caf i David mają 100% racji co do mojego twierdzenia, które strncat()nie zawsze kończy się null. Trochę myliłem zachowania strncat()i strncpy()(kolejny powód, dla którego są to funkcje, których należy unikać - mają nazwy, które sugerują podobne zachowania, ale w rzeczywistości zachowują się inaczej w ważny sposób ...). Poprawiłem odpowiedź, aby to poprawić, a także dodać dodatkowe informacje.
Michael Burr

1
Zauważ, że char str[N] = ""; strncat(str, "long string", sizeof(str));jest to przepełnienie buforu, jeśli N nie jest wystarczająco duże. strncat()Funkcja jest zbyt łatwe do nadużycia; nie należy go używać. Jeśli możesz strncat()bezpiecznie używać , możesz użyć memmove()lub memcpy()zamiast tego (i te byłyby bardziej wydajne).
Jonathan Leffler

9

Standardowe funkcje biblioteczne, których nigdy nie należy używać:

setjmp.h

  • setjmp(). Razem z longjmp()tymi funkcjami są powszechnie uznawane za niezwykle niebezpieczne w użyciu: prowadzą do programowania spaghetti, mają wiele form niezdefiniowanych zachowań, mogą powodować niezamierzone skutki uboczne w środowisku programu, takie jak wpływ na wartości przechowywane na stosie. Odniesienia: MISRA-C: 2012 reguła 21.4, CERT C MSC22-C .
  • longjmp(). Zobacz setjmp().

stdio.h

  • gets(). Funkcja została usunięta z języka C (zgodnie z C11), ponieważ była niebezpieczna zgodnie z projektem. Funkcja została już oznaczona jako przestarzała w C99. Użyj fgets()zamiast tego. Odniesienia: ISO 9899: 2011 K.3.5.4.1, zob. Także przypis 404.

stdlib.h

  • atoi()rodzina funkcji. Nie obsługują one błędów, ale wywołują niezdefiniowane zachowanie, gdy wystąpią błędy. Funkcje całkowicie zbędne, które można zastąpić strtol()rodziną funkcji. Odniesienia: MISRA-C: 2012 zasada 21.7.

string.h

  • strncat(). Ma niezręczny interfejs, który jest często niewłaściwie używany. Jest to przeważnie funkcja zbędna. Zobacz także uwagi dotyczące strncpy().
  • strncpy(). Celem tej funkcji nigdy nie było być bezpieczniejszą wersją strcpy(). Jego jedynym celem była zawsze obsługa starożytnego formatu ciągów znaków w systemach uniksowych, a fakt, że został włączony do standardowej biblioteki, jest znanym błędem. Ta funkcja jest niebezpieczna, ponieważ może pozostawić ciąg bez zakończenia zerowego, a programiści często używają jej nieprawidłowo. Odniesienia: Dlaczego strlcpy i strlcat są uważane za niezabezpieczone? .

Standardowe funkcje biblioteczne, których należy używać ostrożnie:

assert.h

  • assert(). Obejmuje narzut i generalnie nie powinien być używany w kodzie produkcyjnym. Lepiej jest użyć programu obsługi błędów specyficznego dla aplikacji, który wyświetla błędy, ale niekoniecznie zamyka cały program.

signal.h

stdarg.h

  • va_arg()rodzina funkcji. Obecność funkcji o zmiennej długości w programie w języku C prawie zawsze wskazuje na słaby projekt programu. Należy tego unikać, chyba że masz bardzo szczególne wymagania.

stdio.h
Ogólnie rzecz biorąc, cała ta biblioteka nie jest zalecana do kodu produkcyjnego , ponieważ występuje w wielu przypadkach źle zdefiniowanego zachowania i niskiego bezpieczeństwa typów.

  • fflush(). Idealnie nadaje się do strumieni wyjściowych. Wywołuje niezdefiniowane zachowanie, jeśli jest używane dla strumieni wejściowych.
  • gets_s(). Bezpieczna wersja gets()zawarta w interfejsie sprawdzania granic C11. Zalecane jest użycie fgets()zamiast tego, zgodnie ze standardowymi zaleceniami C. Odniesienia: ISO 9899: 2011 K.3.5.4.1.
  • printf()rodzina funkcji. Funkcje ciężkie, które wiążą się z wieloma niezdefiniowanymi zachowaniami i słabym bezpieczeństwem typów. sprintf()ma również luki. Funkcji tych należy unikać w kodzie produkcyjnym. Odniesienia: MISRA-C: 2012 reguła 21.6.
  • scanf()rodzina funkcji. Zobacz uwagi na temat printf(). Ponadto - scanf()jest podatny na przepełnienie bufora, jeśli nie jest używany poprawnie. fgets()jest preferowane w miarę możliwości. Odniesienia: CERT C INT05-C , MISRA-C: 2012 zasada 21.6.
  • tmpfile()rodzina funkcji. Zawiera różne luki w zabezpieczeniach. Źródła : CERT C FIO21-C .

stdlib.h

  • malloc()rodzina funkcji. Idealnie nadaje się do użycia w systemach hostowanych, chociaż pamiętaj o dobrze znanych problemach w C90 i dlatego nie rzucaj wyniku . malloc()Rodzina funkcji nie powinny być używane w samodzielnym aplikacji. Odniesienia: MISRA-C: zasada z 2012 r. 21.3.

    Zauważ również, że realloc()jest to niebezpieczne w przypadku nadpisania starego wskaźnika wynikiem realloc(). W przypadku niepowodzenia funkcji tworzysz wyciek.

  • system(). Wiąże się z dużym narzutem i chociaż jest przenośny, często lepiej jest zamiast tego używać funkcji API specyficznych dla systemu. Pochodzi z różnymi źle zdefiniowanymi zachowaniami. Źródła : CERT C ENV33-C .

string.h

  • strcat(). Zobacz uwagi dotyczące strcpy().
  • strcpy(). Idealnie nadaje się do użycia, chyba że rozmiar danych do skopiowania jest nieznany lub większy niż bufor docelowy. Jeśli nie zostanie wykonane sprawdzenie rozmiaru danych przychodzących, mogą wystąpić przepełnienia buforu. Nie jest to wina strcpy()samego siebie, ale aplikacji wywołującej - to strcpy()jest niebezpieczne, to głównie mit stworzony przez Microsoft .
  • strtok(). Zmienia ciąg wywołujący i używa wewnętrznych zmiennych stanu, co może uczynić go niebezpiecznym w środowisku wielowątkowym.

Sugeruję zalecenie static_assert()w miejsce assert(), jeśli warunek można rozwiązać w czasie kompilacji. Ponadto sprintf()prawie zawsze można go wymienić, snprintf()co jest nieco bezpieczniejsze.
user694733

1
Użycie strtok()w funkcji A oznacza, że ​​(a) funkcja nie może wywołać żadnej innej funkcji, która również używa, strtok()gdy A jej używa, oraz (b) oznacza, że ​​żadna funkcja, która wywołuje A, nie może użyć, strtok()gdy wywołuje A. Innymi słowy, użycie strtok()zatruwa łańcuch połączeń; nie może być bezpiecznie używany w kodzie biblioteki, ponieważ musi udokumentować, którego używa, strtok()aby uniemożliwić innym użytkownikom strtok()wywoływanie kodu biblioteki.
Jonathan Leffler

strncpyJest właściwa funkcja używać podczas pisania zerowej wyściełany bufor ciągiem z danych pobranych z obu zerowej zakończony sznurkiem lub zero-wyściełane bufor, którego wielkość jest co najmniej tak duża jak przeznaczenia. Odbojniki z zerowym wypełnieniem nie są zbyt powszechne, ale nie są też dokładnie niejasne.
supercat

7

Niektórzy twierdzą, że tak strcpyi strcatnależy ich unikać, na korzyść strncpyi strncat. Moim zdaniem jest to nieco subiektywne.

Zdecydowanie należy ich unikać w przypadku wprowadzania danych przez użytkownika - bez wątpienia tutaj.

W kodzie „daleko” od użytkownika, gdy po prostu wiesz, że bufory są wystarczająco długie strcpyi strcatmoże być nieco bardziej wydajne, ponieważ obliczanie, które nma przekazać ich kuzynom, może być zbędne.


4
I jeśli to możliwe, jeszcze lepiej strlcati strlcpy, ponieważ „n” wersja nie gwarantuje NULL zakończenie łańcucha docelowego.
Dan Andreatta

Brzmi jak przedwczesna optymalizacja. Ale IIRC strncpy()zapisze dokładnie nbajty, używając w razie potrzeby znaków nul. Jak Dan, używanie bezpiecznej wersji jest najlepszym wyborem IMO.
Bastien Léonard

@Dan, te funkcje są niestety niestandardowe i dlatego nie należą do tej dyskusji
Eli Bendersky

@Eli: ale fakt, że strncat()prawidłowe i bezpieczne używanie może być trudne (i należy go unikać) jest na temat.
Michael Burr

@Michael: Nie powiedziałbym, że trudno jest używać go poprawnie i bezpiecznie. Ostrożnie, to działa dobrze
Eli Bendersky

6

Uniknąć

  • strtok dla programów wielowątkowych, ponieważ nie jest bezpieczny dla wątków.
  • gets ponieważ może to spowodować przepełnienie bufora

2
Są to nieco inne przypadki. Możesz bezpiecznie używać strtok, jeśli wiesz, że twój program nie jest wielowątkowy lub w jakiś sposób blokujesz do niego dostęp, ale nie możesz używać go bezpiecznie, więc nigdy nie powinieneś go używać.
jcoder

9
Problem strtok()wykracza trochę poza bezpieczeństwo wątków - nie jest to bezpieczne nawet w programie z pojedynczym wątkiem, chyba że masz pewność, że żadne funkcje, które Twój kod może wywołać podczas używania strtok(), również go nie używają (bo inaczej zepsują strtok()stan spod ciebie). W rzeczywistości większość kompilatorów, które są przeznaczone dla platform wielowątkowych, rozwiązuje strtok()potencjalne problemy dotyczące wątków, wykorzystując lokalną pamięć wątkową dla strtok()statycznych danych. Ale to nadal nie rozwiązuje problemu innych funkcji używających go, gdy jesteś (w tym samym wątku).
Michael Burr

Rzeczywiście i teraz się zamknę, ponieważ nie chcę nikogo zachęcać do używania strtok, ponieważ cierpi na wiele problemów. Chciałem tylko zwrócić uwagę, że różni się od dostaje chociaż jak to jest możliwe, aby go używać bezpiecznie, podczas gdy to jest niemożliwe do zastosowania dostaje bezpiecznie.
jcoder

@Jimmy: Często istnieją niestandardowe rozszerzenia bibliotek lub możesz napisać własne. Dużym problemem strtok()jest to, że utrzymuje dokładnie jeden bufor do pracy, więc większość dobrych zamienników wymaga utrzymywania wartości bufora i przekazywania jej.
David Thornley

strcspnrobi większość tego, czego potrzebujesz - znajdź następny separator tokenów. Możesz z nim ponownie zaimplementować rozsądny wariant strtok.
bluss

5

Prawdopodobnie warto jeszcze raz dodać, że strncpy()nie jest to zamiennik ogólnego przeznaczenia, strcpy()który może sugerować jego nazwa. Jest przeznaczony dla pól o stałej długości, które nie wymagają terminatora nul (pierwotnie został zaprojektowany do użytku z wpisami katalogu UNIX, ale może być przydatny do takich rzeczy, jak pola kluczy szyfrowania).

Jest jednak łatwy w użyciu strncat()jako zamiennik dla strcpy():

if (dest_size > 0)
{
    dest[0] = '\0';
    strncat(dest, source, dest_size - 1);
}

( ifTest można oczywiście porzucić w typowym przypadku, gdy wiesz, że dest_sizejest to zdecydowanie niezerowe).


5

Sprawdź także listę zabronionych interfejsów API firmy Microsoft . Są to interfejsy API (w tym wiele już wymienionych tutaj), które są zakazane w kodzie firmy Microsoft, ponieważ są często nadużywane i prowadzą do problemów z bezpieczeństwem.

Możesz nie zgadzać się z nimi wszystkimi, ale wszystkie są warte rozważenia. Dodają API do listy, gdy jego niewłaściwe użycie doprowadziło do wielu błędów bezpieczeństwa.


2

Prawie każda funkcja obsługująca ciągi zakończone znakiem NUL jest potencjalnie niebezpieczna. Jeśli otrzymujesz dane ze świata zewnętrznego i manipulujesz nimi za pomocą funkcji str * (), to ustawisz się na katastrofę


2

Nie zapominaj o sprintfie - to przyczyna wielu problemów. Jest to prawdą, ponieważ alternatywa, snprintf ma czasami różne implementacje, które mogą uniemożliwić przenoszenie kodu.

  1. linux: http://linux.die.net/man/3/snprintf

  2. windows: http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx

W przypadku 1 (linux) zwracana wartość to ilość danych potrzebna do przechowywania całego bufora (jeśli jest mniejsza niż rozmiar danego bufora, to wyjście zostało obcięte)

W przypadku 2 (Windows) wartość zwracana jest liczbą ujemną w przypadku obcięcia danych wyjściowych.

Generalnie należy unikać funkcji, które nie są:

  1. bezpieczne przepełnienie bufora (wiele funkcji jest już tutaj wspomnianych)

  2. bezpieczny wątek / niepowtarzalny (na przykład strtok)

W instrukcji każdej funkcji należy szukać słów kluczowych, takich jak: safe, sync, async, thread, buffer, bugs


2
_sprintf()został stworzony przez Microsoft przed pojawieniem się standardu snprintf()IIRC. ´StringCbPrintf () ´ jest dość podobne do snprintf()chociaż.
Bastien Léonard

sprintfw niektórych przypadkach możesz bezpiecznie używać : sprintf(buffer,"%10s",input);ogranicza kopiowane bajty nb do 10 (jeśli bufferjest char buffer[11]to bezpieczne, nawet jeśli dane mogą zostać obcięte).
Jean-François Fabre

2

Bardzo trudno jest go scanfbezpiecznie używać . Dobre użycie scanfpozwala uniknąć przepełnienia bufora, ale nadal jesteś podatny na niezdefiniowane zachowanie podczas odczytywania liczb, które nie pasują do żądanego typu. W większości przypadków, fgetsa następnie samodzielnego analizowania (z wykorzystaniem sscanf, strchritp) jest lepszym rozwiązaniem.

Ale nie powiedziałbym „unikaj przez scanfcały czas”. scanfma swoje zastosowania. Na przykład, powiedzmy, że chcesz odczytać dane wejściowe użytkownika w chartablicy o długości 10 bajtów. Chcesz usunąć końcowy znak nowej linii, jeśli taki istnieje. Jeśli użytkownik wprowadzi więcej niż 9 znaków przed nową linią, chcesz zachować pierwszych 9 znaków w buforze i odrzucić wszystko do następnej nowej linii. Możesz to zrobić:

char buf[10];
scanf("%9[^\n]%*[^\n]", buf));
getchar();

Kiedy już przyzwyczaisz się do tego idiomu, jest on krótszy i pod pewnymi względami czystszy niż:

char buf[10];
if (fgets(buf, sizeof buf, stdin) != NULL) {
    char *nl;
    if ((nl = strrchr(buf, '\n')) == NULL) {
        int c;
        while ((c = getchar()) != EOF && c != '\n') {
            ;
        }
    } else {
        *nl = 0;
    }
}

0

We wszystkich scenariuszach kopiowania / przenoszenia ciągów - strcat (), strncat (), strcpy (), strncpy (), itp. - wszystko idzie znacznie lepiej ( bezpieczniej ), jeśli narzuconych jest kilka prostych heurystyk:

   1. Zawsze wypełnianie NUL Twój bufor (y) przed dodaniem danych.
   2. Zadeklaruj bufory znaków jako [SIZE + 1], ze stałą makro.

Na przykład, biorąc pod uwagę:

#define   BUFSIZE   10
char      Buffer[BUFSIZE+1] = { 0x00 };  /* The compiler NUL-fills the rest */

możemy użyć kodu takiego jak:

memset(Buffer,0x00,sizeof(Buffer));
strncpy(Buffer,BUFSIZE,"12345678901234567890");

stosunkowo bezpiecznie. Memset () powinno pojawić się przed strncpy (), mimo że zainicjowaliśmy Bufor w czasie kompilacji, ponieważ nie wiemy, jakie śmieci umieścił w nim inny kod przed wywołaniem naszej funkcji. Strncpy () obetnie skopiowane dane do "1234567890" i nie zakończy ich NUL. Jednakże, ponieważ już zapełniliśmy cały bufor NUL - sizeof (Buffer), zamiast BUFSIZE - jest gwarantowane, że i tak będzie końcowe „poza zakresem” kończące NUL, o ile ograniczymy nasze zapisy za pomocą BUFSIZE stała, zamiast sizeof (Buffer).

Bufor i BUFSIZE również działają dobrze dla snprintf ():

memset(Buffer,0x00,sizeof(Buffer));
if(snprintf(Buffer,BUFIZE,"Data: %s","Too much data") > BUFSIZE) {
    /* Do some error-handling */
}   /* If using MFC, you need if(... < 0), instead */

Mimo że snprintf () zapisuje tylko znaki BUFIZE-1, po których występuje NUL, działa to bezpiecznie. Więc „marnujemy” zbędny bajt NUL na końcu bufora… zapobiegamy zarówno przepełnieniu bufora, jak i warunkom niezakończonego łańcucha, za całkiem mały koszt pamięci.

Moje wywołanie strcat () i strncat () jest bardziej twarde: nie używaj ich. Trudno jest bezpiecznie używać strcat (), a API dla strncat () jest tak sprzeczne z intuicją, że wysiłek potrzebny do prawidłowego użycia go neguje wszelkie korzyści. Proponuję następujące drop-in:

#define strncat(target,source,bufsize) snprintf(target,source,"%s%s",target,source)

Tworzenie drop-in strcat () jest kuszące, ale nie jest to dobry pomysł:

#define strcat(target,source) snprintf(target,sizeof(target),"%s%s",target,source)

ponieważ target może być wskaźnikiem (w ten sposób sizeof () nie zwraca potrzebnych nam informacji). Nie mam dobrego "uniwersalnego" rozwiązania dla instancji strcat () w twoim kodzie.

Problemem, który często napotykam u programistów korzystających z "strFunc () - świadomych", jest próba ochrony przed przepełnieniem bufora za pomocą strlen (). Jest to w porządku, jeśli zawartość ma gwarancję zakończenia NUL. W przeciwnym razie sama strlen () może spowodować błąd przepełnienia bufora (zwykle prowadzący do naruszenia segmentacji lub innej sytuacji zrzutu pamięci), zanim dotrzesz do „problematycznego” kodu, który próbujesz chronić.


-2

atoi nie jest bezpieczna wątkowo. Zamiast tego używam strtol, zgodnie z zaleceniem ze strony podręcznika.


5
Wygląda na to, że dotyczy to jednej konkretnej implementacji. Nie ma powodu, dla którego strtol()miałby być bezpieczny dla wątków i atoi()nie byłby.
David Thornley,

2
Zalecenie użycia strtol nie ma nic wspólnego z bezpieczeństwem wątków, ale z obsługą błędów. Nie jestem pewien, z której strony man masz te informacje - nie mogę znaleźć poniżej żadnej rekomendacji man atoi(chociaż powinno być).
Lundin
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.