Dlaczego powinieneś używać strncpy zamiast strcpy?

84

Edycja: dodałem źródło dla przykładu.

Natknąłem się na ten przykład :

Który wyprodukował ten wynik:

miejsce docelowe to pierwotnie = 'abcdefg'
Po strcpy miejsce docelowe staje się „123456789”

miejsce docelowe1 pierwotnie = 'abcdefg'
Po strncpy miejsce docelowe1 zmienia się na „12345fg”

Zastanawiam się, dlaczego ktoś miałby chcieć tego efektu. Wygląda na to, że byłoby to zagmatwane. Ten program sprawia, że ​​myślę, że można by w zasadzie skopiować czyjeś nazwisko (np. Tom Brokaw) z Tomem Bro763.

Jakie są zalety używania strncpy() nad strcpy() ?

Kredns
źródło
81
Myślę, że chciałeś zapytać „Dlaczego u licha ktoś miałby używać strcpyzamiast strncpy?”.
Sam Harwell,
5
Kiedy byłem asystentem technicznym na pierwszym semestrze kursu programowania w C, zapewniałem moich studentów, że jakiekolwiek użycie takich metod getlinespowoduje nieprawidłowe wyniki, gdy oceniam je na podstawie starannie opracowanych danych wejściowych. :)
Sam Harwell,
4
Myślę, że źle zrozumiałeś, co właściwie robi kod. Przyjrzyj się bliżej.
Emil H
6
Szkoda, że ​​C nigdy nie dostał w połowie przyzwoitej standardowej biblioteki dla łańcuchów.
starblue
7
to nie jest aż tak wielka szkoda. Chodzi mi o to, że całkowicie mnie to złamało i sprawiło, że języki wyższego poziomu ZNACZNIE fajniej :)
Carson Myers

Odpowiedzi:

98

strncpyzwalcza przepełnienie bufora, wymagając podania w nim długości. strcpyzależy od końcowego \0, co nie zawsze może wystąpić.

Po drugie, nie rozumiem, dlaczego zdecydowałeś się skopiować tylko 5 znaków z 7-znakowego ciągu, ale to daje oczekiwane zachowanie. To tylko kopiowanie pierwszych nznaków, gdzie njest trzeci argument.

Wszystkie nfunkcje są używane do kodowania obronnego przed przepełnieniem bufora. Należy ich używać zamiast starszych funkcji, takich jak strcpy.

Eric
źródło
47
Zobacz lysator.liu.se/c/rat/d11.html : strncpyzostał początkowo wprowadzony do biblioteki C, aby zajmować się polami nazw o stałej długości w strukturach takich jak pozycje katalogów. Takie pola nie są używane w taki sam sposób jak łańcuchy: końcowe null jest niepotrzebne dla pola o maksymalnej długości, a ustawienie końcowych bajtów dla krótszych nazw na wartość null zapewnia wydajne porównania pod względem pola. strncpynie jest z pochodzenia „ograniczonym strcpy”, a Komitet wolał raczej uznać istniejącą praktykę niż zmienić jej funkcję, aby lepiej dostosować ją do takiego zastosowania.
Sinan Ünür
35
Nie jestem pewien, dlaczego zbiera to dużo głosów w górę - strncpy nigdy nie było pomyślane jako bezpieczniejsza alternatywa dla strcpy i w rzeczywistości nie jest bezpieczniejsze, ponieważ nie kończy zerowego ciągu. Ma również inną funkcjonalność, ponieważ uzupełnia dostarczoną długość znakami NUL. Jak caf mówi w swojej odpowiedzi - służy do nadpisywania ciągów znaków w tablicy o stałym rozmiarze.
Dipstick
26
Faktem jest, że niestrncpy jest to bezpieczniejsza wersja . strcpy
Sinan Ünür
7
@Sinan: Nigdy nie powiedziałem, że to bezpieczniejsze. To obronne. Zmusza cię do wydłużenia, a więc do myślenia o tym, co robisz. Są lepsze rozwiązania, ale faktem jest, że ludzie używają (i robią) strncpyzamiast, strcpyponieważ jest to znacznie bardziej defensywna funkcja ... tak właśnie powiedziałem.
Eric
10
Wszystkie n funkcji są używane jako kodowanie obronne przed przepełnieniem bufora. Proszę używać ich zamiast starszych funkcji, takich jak strcpy. To prawda snprintf, ale nie ma to znaczenia strncati jest całkowicie nieprawdziwe strncpy. Jak ta odpowiedź mogła kiedykolwiek dostać tyle głosów pozytywnych? Pokazuje, jak zła jest sytuacja dotycząca tej fałszywej funkcji. Używanie go nie jest obronne: w większości sytuacji programista nie rozumie jego semantyki i tworzy potencjalnie niezerowy ciąg zakończony.
chqrlie
180

strncpy()Funkcja została zaprojektowana z bardzo konkretnego problemu pamiętać: manipulowania ciągi przechowywane w sposób oryginalnych wpisów do katalogów UNIX. Używały one tablicy o stałym rozmiarze, a terminator nul był używany tylko wtedy, gdy nazwa pliku była krótsza niż tablica.

To właśnie kryje się za dwiema osobliwościami strncpy():

  • Nie umieszcza terminatora nul w miejscu docelowym, jeśli jest całkowicie wypełniony; i
  • Zawsze całkowicie wypełnia miejsce docelowe, w razie potrzeby wpisując wartości nuls.

Dla „bezpieczniejszego strcpy()” lepiej jest używać w następujący strncat()sposób:

To zawsze zakończy zerowanie wyniku i nie skopiuje więcej niż to konieczne.

kawiarnia
źródło
Ale oczywiście strncpy nie zawsze jest tym, czego chcesz: strncpy akceptuje maksymalną liczbę znaków do dodania, a nie rozmiar bufora docelowego ... Ale to tylko drobiazg, więc prawdopodobnie nie będzie problemem, chyba że ty próbuję połączyć jeden ciąg z innym.
David Wolever,
Nie wiedziałem, dlaczego tak się dzieje i jest to bardzo istotne dla tego, nad czym pracuję w bankomacie.
Matt Joiner,
Funkcja strncpy () została zaprojektowana do przechowywania ciągów znaków w formacie z wypełnieniem zerowym o stałej długości. Taki format był używany w oryginalnych wpisach katalogów systemu Unix, ale jest również używany w niezliczonych innych miejscach, ponieważ pozwala na przechowywanie ciągu 0-N bajtów w pamięci N bajtów. Nawet dzisiaj wiele baz danych używa ciągów znaków wypełnionych zerami w swoich polach ciągów o stałej długości. Zamieszanie z strncpy () wynika z faktu, że konwertuje ona łańcuchy do formatu FLNP. Jeśli potrzebny jest ciąg FLNP, to wspaniale. Jeśli potrzebny jest ciąg zakończony zerem, należy samemu zapewnić zakończenie.
supercat
1
dlaczego musimy pisać dest[0] = '\0';przed wywołaniem strncat? Czy mógłby pan to wyjaśnić?
snr
4
@snr: strncat()łączy ciąg źródłowy na końcu ciągu docelowego. Chcemy tylko skopiować ciąg źródłowy do miejsca docelowego, więc najpierw ustawiamy miejsce docelowe na pusty ciąg - to właśnie dest[0] = '\0';robi.
kawiarnia
34

Chociaż znam intencję, która się za tym kryje strncpy, nie jest to dobra funkcja. Unikaj obu. Raymond Chen wyjaśnia .

Osobiście, moim wnioskiem jest po prostu unikanie strncpyi wszystkich jego przyjaciół, jeśli masz do czynienia z ciągami znaków zakończonymi zerem. Pomimo „str” w nazwie, funkcje te nie generują ciągów zakończonych znakiem null. Konwertują ciąg zakończony zerem na bufor znaków surowych. Używanie ich, gdy oczekuje się łańcucha zakończonego znakiem null, ponieważ drugi bufor jest po prostu nieprawidłowy. Nie tylko nie uda ci się uzyskać prawidłowego zakończenia zerowania, jeśli źródło jest zbyt długie, ale jeśli źródło jest krótkie, otrzymasz niepotrzebne wypełnienie null.

Zobacz także Dlaczego strncpy jest niepewne?

Sinan Ünür
źródło
27

strncpy NIE jest bezpieczniejsze niż strcpy, po prostu zamienia jeden typ błędów na inny. W C, obsługując łańcuchy C, musisz znać rozmiar swoich buforów, nie ma sposobu na obejście tego. strncpy było uzasadnione w przypadku katalogu wspomnianego przez innych, ale poza tym nigdy nie powinieneś go używać:

  • jeśli znasz długość swojego łańcucha i bufora, po co używać strncpy? W najlepszym przypadku jest to strata mocy obliczeniowej (dodawanie bezużytecznych 0)
  • jeśli nie znasz długości, ryzykujesz ciche obcięcie napisów, co nie jest dużo lepsze niż przepełnienie bufora
David Cournapeau
źródło
Myślę, że to dobry opis strncpy, więc zagłosowałem za tym. strncpy ma swój własny zestaw problemów. Myślę, że to jest powód, dla którego np. Glib ma swoje własne rozszerzenia. I tak, to niefortunne, że jako programista musisz być świadomy rozmiaru wszystkich tablic. Decyzja o 0 zakończonej tablicy znaków jako łańcuchu drogo nas wszystkich kosztowała ....
Friedrich
1
Ciągi wypełnione zerami są dość powszechne podczas przechowywania danych w plikach o stałym formacie. Z pewnością popularność takich rzeczy, jak silniki baz danych i XML, wraz ze zmieniającymi się oczekiwaniami użytkowników spowodowała, że ​​pliki o stałym formacie są mniej powszechne niż 20 lat temu. Niemniej jednak takie pliki są często najbardziej efektywnym czasowo sposobem przechowywania danych. Z wyjątkiem sytuacji, gdy istnieje ogromna rozbieżność między oczekiwaną a maksymalną długością danych w rekordzie, znacznie szybciej jest odczytać rekord jako pojedynczy fragment zawierający nieużywane dane niż odczytać rekord podzielony na wiele fragmentów.
supercat
Po prostu przejął opiekę nad starym kodem, który używał g_strlcpy (), więc nie cierpi z powodu nieefektywności wypełniania, ale na pewno liczba przesłanych bajtów NIE została utrzymana, więc kod po cichu obcinał wynik.
user2548100
21

To, czego szukasz, to funkcja, strlcpy()która zawsze kończy ciąg z 0 i inicjalizuje bufor. Jest również w stanie wykryć przepełnienia. Jedynym problemem jest to, że nie jest (naprawdę) przenośny i występuje tylko w niektórych systemach (BSD, Solaris). Problem z tą funkcją polega na tym, że otwiera kolejną puszkę robaków, co widać w dyskusjach na http://en.wikipedia.org/wiki/Strlcpy

Osobiście uważam, że jest znacznie bardziej przydatny niż strncpy()i strcpy(). Ma lepszą wydajność i jest dobrym towarzyszem snprintf(). W przypadku platform, które go nie mają, jest to stosunkowo łatwe do wdrożenia. (w fazie rozwoju aplikacji zastępuję te dwie funkcje ( snprintf()i strlcpy()) wersją trappingową, która brutalnie przerywa program w przypadku przepełnienia bufora lub obcięcia. Pozwala to szybko wyłapać najgorszych przestępców. Szczególnie jeśli pracujesz na bazie kodu innej osoby .

EDYCJA: strlcpy()można łatwo zaimplementować:

Patrick Schlüter
źródło
3
Mógłbyś napisać, że strlcpy jest dostępny na prawie wszystkim innym niż Linux i Windows! Jest jednak na licencji BSD, więc możesz po prostu upuścić go do jednej ze swoich bibliotek i stamtąd używać.
Michael van der Westhuizen
Możesz dodać test dstsize > 0i nic nie robić, jeśli nie jest.
chqrlie
Tak, masz rację. Dodam sprawdzenie, ponieważ bez niego a dstsizespowoduje wywołanie memcpydługości lenw buforze docelowym i przepełnienie go.
Patrick Schlüter
Plus jeden za promowanie dobrych rozwiązań. Więcej ludzi musi wiedzieć o strlcpy, ponieważ wszyscy słabo wymyślają go na nowo.
rsp
@MichaelvanderWesthuizen Jest dostępny w systemie Linux, ale nie w glibc. Zobacz moje odpowiedzi, aby uzyskać więcej informacji (1) (2) (3)
rsp
3

Ta strncpy()funkcja jest bezpieczniejsza: musisz podać maksymalną długość, jaką może zaakceptować bufor docelowy. W przeciwnym razie może się zdarzyć, że łańcuch źródłowy nie zostanie poprawnie zakończony zerem. W takim przypadku strcpy()funkcja może zapisać więcej znaków do miejsca docelowego, uszkadzając wszystko, co znajduje się w pamięci za buforem docelowym. Jest to problem przepełnienia bufora używany w wielu exploitach

Również dla funkcji API POSIX, takich jak, read()które nie umieszczają kończącego 0 w buforze, ale zwracają liczbę odczytanych bajtów, możesz albo ręcznie wstawić 0, albo skopiować go używając strncpy().

W Twoim przykładowym kodzie w indexrzeczywistości nie jest indeksem, ale count- mówi, ile maksymalnie znaków ma skopiować ze źródła do celu. Jeśli wśród pierwszych n bajtów źródła nie ma bajtu zerowego, ciąg umieszczony w miejscu docelowym nie zostanie zakończony wartością null

CsTamas
źródło
1

strncpy wypełnia miejsce docelowe wartością „\ 0” dla rozmiaru źródła, nawet jeśli rozmiar miejsca docelowego jest mniejszy ....

manpage:

Jeśli długość src jest mniejsza niż n, strncpy () wypełnia pozostałą część dest bajtami zerowymi.

i nie tylko reszta ... także po tym, aż do osiągnięcia n znaków. W ten sposób otrzymujesz przepełnienie ... (zobacz implementację strony podręcznika)

Jeronimo
źródło
3
strncpy wypełnia miejsce docelowe „\ 0” dla rozmiaru źródła, chociaż rozmiar miejsca docelowego jest mniejszy .... Obawiam się, że to stwierdzenie jest błędne i mylące: strncpywypełnia miejsce docelowe za pomocą „\ 0” dla argument size, jeśli długość źródła jest mniejsza. Argument rozmiar nie jest rozmiarem źródła, a nie maksymalną liczbą znaków do skopiowania ze źródła, ponieważ jest strncatto rozmiar miejsca docelowego.
chqrlie
@chqrlie: Dokładnie. Zaletą strncpynad innymi operacjami kopiowania jest to, że gwarantuje, że cała lokalizacja docelowa zostanie zapisana. Ponieważ kompilatory mogą próbować wykazać się „kreatywnością” podczas kopiowania struktur zawierających pewne nieokreślone wartości, zapewnienie pełnego zapisu wszystkich tablic znaków w strukturach może być najprostszym sposobem uniknięcia „niespodzianek”.
supercat
@supercat: bardzo mała zaleta w tym konkretnym przypadku ... ale miejsce docelowe musi zostać poprawione po wywołaniu, strncpyaby zapewnić zerowe zakończenie: strncpy(dest, src, dest_size)[dest_size - 1] = '\0';
chqrlie
@chqrlie: To, czy końcowy bajt zerowy byłby wymagany, zależy od tego, co mają reprezentować dane. Używanie w strukturze danych uzupełnianych zerami zamiast danych zakończonych zerem nie jest tak powszechne, jak było kiedyś, ale jeśli np. Format pliku obiektowego używa 8-bajtowych nazw sekcji, możliwość posiadania char[8]wewnątrz struktury radzi sobie do 8 znaków może być ładniejsze niż użycie a, char[8]ale tylko możliwość obsługi 7 znaków lub konieczność skopiowania ciągu do char[9]bufora, a następnie memcpydo miejsca docelowego.
supercat
@chqrlie: Większość kodu, który robi rzeczy z łańcuchami, powinna wiedzieć, jak długie mogą one być, i nie powinien ślepo uruchamiać się ze charwskaźnikami, dopóki nie osiągną zera. Te tylko rzeczą zerowej zakończone łańcuchy są naprawdę dobre, jest literały ciągów znaków, a nawet istnieje zmiennej długości kodowane prefiks będzie prawdopodobnie lepiej. W przypadku prawie wszystkiego innego byłoby lepiej, gdyby łańcuchy były poprzedzone długością lub miały specjalny prefiks, który wskazywałby, że char*jest to coś podobnego struct stringInfo {char header[4]; char *realData; size_t length; size_t size;}.
supercat
-1

Może to być używane w wielu innych scenariuszach, w których musisz skopiować tylko część oryginalnego ciągu do miejsca docelowego. Używając strncpy () możesz skopiować ograniczoną część oryginalnego ciągu w przeciwieństwie do strcpy (). Widzę, że wysłany przez Ciebie kod pochodzi z witryny publib.boulder.ibm.com .

ARV
źródło
-1

To zależy od naszych wymagań. Dla użytkowników systemu Windows

Używamy strncpy zawsze, gdy nie chcemy kopiować całego ciągu lub chcemy skopiować tylko n liczby znaków. Ale strcpy kopiuje cały ciąg, w tym kończący znak null.

Te linki pomogą Ci dowiedzieć się więcej o strcpy i strncpy oraz o tym, gdzie możemy ich użyć.

o strcpy

o strncpy

Prakash
źródło
-8

strncpy jest bezpieczniejszą wersją strcpy w rzeczywistości nigdy nie powinieneś używać strcpy, ponieważ jego potencjalna luka w przepełnieniu bufora sprawia, że ​​system jest podatny na wszelkiego rodzaju ataki

bashmohandes
źródło
6
Zobacz lysator.liu.se/c/rat/d11.html : Funkcja strncpy strncpy została początkowo wprowadzona do biblioteki C, aby radzić sobie z polami nazw o stałej długości w strukturach takich jak pozycje katalogu. Takie pola nie są używane w taki sam sposób jak łańcuchy: końcowe wartości null nie są potrzebne w przypadku pól o maksymalnej długości, a ustawienie wartości null na końcowych bajtach dla krótszych nazw zapewnia wydajne porównania pod względem pól. strncpy nie jest z pochodzenia `` ograniczonym strcpy '', a Komitet wolał uznać istniejącą praktykę, zamiast zmieniać funkcję, aby lepiej dostosować ją do takiego zastosowania.
Sinan Ünür