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.