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?
c
standard-library
obsolete
Andrei Ciobanu
źródło
źródło
strncpy()
jako ogólnego zamiennikastrcpy()
i nie użyłbymstrncat()
nigdy, ponieważ ma najbardziej nieintuicyjny interfejs, jaki można sobie wyobrazić - czy WIESZ, co określa parametr długości?Odpowiedzi:
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:
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.
źródło
strncpy
generalnie 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ść).strncpy()
niestrncat()
jest sensownym zamiennikiem ani tym gorzej .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 ():
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).
źródło
strncpy()
, ale się myliszstrncat()
.strncat()
nie został zaprojektowany do użytku z polami o stałej długości - w rzeczywistości został zaprojektowany tak, abystrcat()
ograniczał liczbę łączonych znaków. Dość łatwo jest używać tego jako „bezpiecznegostrcat()
”, śledząc miejsce pozostające w buforze podczas wykonywania wielu konkatenacji, a nawet łatwiej jest używać go jako „bezpiecznegostrcpy()
” (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'
.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.strncat()
będzie działać poprawnie niezależnie od długości ciągu źródłowego, astrcat()
nie. Problemstrlcat()
polega na tym, że nie jest to standardowa funkcja C.Kilka odpowiedzi sugeruje użycie
strncat()
ponadstrcat()
; Sugerowałbym, żestrncat()
(istrncpy()
) należy również unikać. Ma problemy, które utrudniają prawidłowe użytkowanie i prowadzą do błędów: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, żestrncat()
użycie jest trudniejsze niż powinno, zwłaszcza jeśli wiele elementów zostanie połączonych z miejscem docelowym.s1
wynosistrlen(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()
istrlcpy()
(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()
istrncpy()
: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życiestrncat()
istrncpy()
(między innymi).Kilka linków z dodatkowymi informacjami:
źródło
memmove()
. (Cóż, możesz użyćmemcpy()
w normalnym przypadku, gdy struny są niezależne.)strncat()
zawsze kończy ciąg docelowy.strncat()
. Jednak jest to poprawnestrncpy()
, co ma inne problemy.strncat()
jest rozsądnym zamiennikiemstrcat()
, alestrncpy()
nie jest rozsądnym zamiennikiemstrcpy()
.strncat()
nie zawsze kończy się null. Trochę myliłem zachowaniastrncat()
istrncpy()
(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.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żeszstrncat()
bezpiecznie używać , możesz użyćmemmove()
lubmemcpy()
zamiast tego (i te byłyby bardziej wydajne).Standardowe funkcje biblioteczne, których nigdy nie należy używać:
setjmp.h
setjmp()
. Razem zlongjmp()
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()
. Zobaczsetjmp()
.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żyjfgets()
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ącestrncpy()
.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
signal()
. Odniesienia: MISRA-C: 2012 reguła 21.5, CERT C SIG32-C .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 wersjagets()
zawarta w interfejsie sprawdzania granic C11. Zalecane jest użyciefgets()
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 tematprintf()
. 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 wynikiemrealloc()
. 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ącestrcpy()
.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 winastrcpy()
samego siebie, ale aplikacji wywołującej - tostrcpy()
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.źródło
static_assert()
w miejsceassert()
, jeśli warunek można rozwiązać w czasie kompilacji. Ponadtosprintf()
prawie zawsze można go wymienić,snprintf()
co jest nieco bezpieczniejsze.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życiestrtok()
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żytkownikomstrtok()
wywoływanie kodu biblioteki.strncpy
Jest 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.Niektórzy twierdzą, że tak
strcpy
istrcat
należy ich unikać, na korzyśćstrncpy
istrncat
. 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
strcpy
istrcat
może być nieco bardziej wydajne, ponieważ obliczanie, któren
ma przekazać ich kuzynom, może być zbędne.źródło
strlcat
istrlcpy
, ponieważ „n” wersja nie gwarantuje NULL zakończenie łańcucha docelowego.strncpy()
zapisze dokładnien
bajty, używając w razie potrzeby znaków nul. Jak Dan, używanie bezpiecznej wersji jest najlepszym wyborem IMO.strncat()
prawidłowe i bezpieczne używanie może być trudne (i należy go unikać) jest na temat.Uniknąć
strtok
dla programów wielowątkowych, ponieważ nie jest bezpieczny dla wątków.gets
ponieważ może to spowodować przepełnienie buforaźródło
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żywaniastrtok()
, 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ązujestrtok()
potencjalne problemy dotyczące wątków, wykorzystując lokalną pamięć wątkową dlastrtok()
statycznych danych. Ale to nadal nie rozwiązuje problemu innych funkcji używających go, gdy jesteś (w tym samym wątku).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.strcspn
robi większość tego, czego potrzebujesz - znajdź następny separator tokenów. Możesz z nim ponownie zaimplementować rozsądny wariantstrtok
.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 dlastrcpy()
:if (dest_size > 0) { dest[0] = '\0'; strncat(dest, source, dest_size - 1); }
(
if
Test można oczywiście porzucić w typowym przypadku, gdy wiesz, żedest_size
jest to zdecydowanie niezerowe).źródło
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.
źródło
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ę
źródło
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.
linux: http://linux.die.net/man/3/snprintf
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ą:
bezpieczne przepełnienie bufora (wiele funkcji jest już tutaj wspomnianych)
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
źródło
_sprintf()
został stworzony przez Microsoft przed pojawieniem się standardusnprintf()
IIRC. ´StringCbPrintf () ´ jest dość podobne dosnprintf()
chociaż.sprintf
w niektórych przypadkach możesz bezpiecznie używać :sprintf(buffer,"%10s",input);
ogranicza kopiowane bajty nb do 10 (jeślibuffer
jestchar buffer[11]
to bezpieczne, nawet jeśli dane mogą zostać obcięte).Bardzo trudno jest go
scanf
bezpiecznie używać . Dobre użyciescanf
pozwala 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,fgets
a następnie samodzielnego analizowania (z wykorzystaniemsscanf
,strchr
itp) jest lepszym rozwiązaniem.Ale nie powiedziałbym „unikaj przez
scanf
cały czas”.scanf
ma swoje zastosowania. Na przykład, powiedzmy, że chcesz odczytać dane wejściowe użytkownika wchar
tablicy 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; } }
źródło
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ć.
źródło
atoi nie jest bezpieczna wątkowo. Zamiast tego używam strtol, zgodnie z zaleceniem ze strony podręcznika.
źródło
strtol()
miałby być bezpieczny dla wątków iatoi()
nie byłby.man atoi
(chociaż powinno być).