Obsługa ciągów C - C string handling

Język programowania C posiada w swojej standardowej bibliotece zestaw funkcji realizujących operacje na łańcuchach (łańcuchach znakowych i łańcuchach bajtów) . Obsługiwane są różne operacje, takie jak kopiowanie, konkatenacja , tokenizacja i wyszukiwanie. Ciągów znakowych, a średnia biblioteki stosuje konwencję, że struny zakończony zerem : ciąg n znaków jest reprezentowany jako matrycy z n + 1 elementów, przy czym ostatni z nich jest postać (z wartością liczbową 0). NUL

Jedyną obsługą ciągów we właściwym języku programowania jest to, że kompilator tłumaczy stałe ciągów w cudzysłowie na ciągi zakończone znakiem NULL.

Definicje

Łańcuch jest zdefiniowany jako ciągła sekwencja jednostek kodu, zakończona pierwszą jednostką kodu zerowego (często nazywaną jednostką kodu NUL ). Oznacza to, że ciąg nie może zawierać jednostki kodu zerowego, ponieważ pierwszy widziany znak oznacza koniec ciągu. Długość od ciąg jest liczba jednostek kodowych przed jednostką kodu zero. Pamięć zajmowana przez ciąg jest zawsze o jedną jednostkę więcej niż długość, ponieważ do przechowywania terminatora zerowego potrzebne jest miejsce.

Ogólnie termin ciąg oznacza ciąg, w którym jednostką kodu jest typ char, czyli dokładnie 8 bitów na wszystkich nowoczesnych maszynach. C90 definiuje szerokie łańcuchy, które używają jednostki kodu typu wchar_t, która na nowoczesnych maszynach ma 16 lub 32 bity. Było to przeznaczone dla Unicode, ale coraz częściej używa się UTF-8 w normalnych ciągach znaków dla Unicode.

Ciągi są przekazywane do funkcji przez przekazanie wskaźnika do pierwszej jednostki kodu. Ponieważ char*i wchar_t*są różnymi typami, funkcje przetwarzające szerokie ciągi różnią się od tych przetwarzających normalne ciągi i mają różne nazwy.

Literały łańcuchowe ( "text"w kodzie źródłowym C) są konwertowane na tablice podczas kompilacji. Wynikiem jest tablica jednostek kodu zawierająca wszystkie znaki oraz jednostkę końcowego kodu zerowego. W C90 L"text"wytwarza szeroki sznurek. Literał ciągu może zawierać jednostkę kodu zerowego (jednym sposobem jest umieszczenie \0go w źródle), ale spowoduje to zakończenie ciągu w tym punkcie. Reszta literału zostanie umieszczona w pamięci (z kolejną jednostką kodu zerowego dodaną na końcu), ale nie można wiedzieć, że te jednostki kodu zostały przetłumaczone z literału łańcuchowego, dlatego taki kod źródłowy nie jest literałem łańcuchowym.

Kodowanie znaków

Każdy ciąg kończy się przy pierwszym wystąpieniu jednostki kodu zerowego odpowiedniego rodzaju ( charlub wchar_t). W konsekwencji ciąg bajtów ( char*) może zawierać znaki inne niż NUL w ASCII lub dowolnym rozszerzeniu ASCII , ale nie znaki w kodowaniach takich jak UTF-16 (nawet jeśli 16-bitowa jednostka kodu może być niezerowa, jej wysoki lub niski bajt może być zero). Kodowania, które mogą być przechowywane w szerokich ciągach, są zdefiniowane przez szerokość wchar_t. W większości implementacji wchar_tjest to co najmniej 16 bitów, a więc wszystkie kodowania 16-bitowe, takie jak UCS-2 , mogą być przechowywane. Jeśli wchar_tjest 32-bitowy , można przechowywać 32-bitowe kodowania, takie jak UTF-32 . (Standard wymaga "typu, który przechowuje dowolny szeroki znak", co w systemie Windows nie jest już prawdziwe od czasu zmiany UCS-2 na UTF-16.) C++11 i C11 dodają dwa typy z jawną szerokością char16_ti char32_t.

Kodowanie o zmiennej szerokości może być używane zarówno w ciągach bajtów, jak i szerokich ciągach. Długość łańcucha i przesunięcia są mierzone w bajtach lub wchar_t, a nie w „znakach”, co może być mylące dla początkujących programistów. UTF-8 i Shift JIS są często używane w ciągach bajtów C, podczas gdy UTF-16 jest często używany w ciągach o szerokości C, gdy wchar_tma 16 bitów. Obcinanie ciągów ze znakami o zmiennej długości przy użyciu funkcji takich jak strncpymoże generować nieprawidłowe sekwencje na końcu ciągu. Może to być niebezpieczne, jeśli obcięte części są interpretowane przez kod, który zakłada, że ​​dane wejściowe są prawidłowe.

Obsługa literałów Unicode, takich jak char foo[512] = "φωωβαρ";(UTF-8) lub wchar_t foo[512] = L"φωωβαρ";(UTF-16 lub UTF-32, zależy od wchar_t) jest zdefiniowana w implementacji i może wymagać, aby kod źródłowy był w tym samym kodowaniu, zwłaszcza gdy charkompilatory mogą po prostu kopiować to, co jest między cytatami. Niektóre kompilatory lub edytory będą wymagały wprowadzenia wszystkich znaków spoza ASCII jako \xNNsekwencji dla każdego bajtu UTF-8 i/lub \uNNNNdla każdego słowa UTF-16. Od C11 (i C++11) dostępna jest nowa char foo[512] = u8"φωωβαρ";składnia literału, która gwarantuje UTF-8 dla literału bytestring.

Przegląd funkcji

Większość funkcji operujących na ciągach C jest deklarowana w string.hnagłówku ( cstringw C++), podczas gdy funkcje operujące na ciągach o szerokości C są deklarowane w wchar.hnagłówku ( cwcharw C++). Te nagłówki zawierają również deklaracje funkcji używanych do obsługi buforów pamięci; nazwa jest więc czymś w rodzaju mylącej nazwy.

Funkcje zadeklarowane w string.hsą niezwykle popularne, ponieważ jako część standardowej biblioteki C gwarantują działanie na dowolnej platformie obsługującej C. Istnieją jednak pewne problemy z bezpieczeństwem tych funkcji, takie jak potencjalne przepełnienia bufora, gdy nie są używane ostrożnie i prawidłowo , przez co programiści preferują bezpieczniejsze i prawdopodobnie mniej przenośne warianty, spośród których kilka popularnych wymieniono poniżej. Niektóre z tych funkcji naruszają również poprawność stałej , akceptując constwskaźnik w łańcuchu i zwracając constniewskaźnik w łańcuchu. Aby to naprawić, niektóre z nich zostały podzielone na dwie przeciążone funkcje w wersji C++ biblioteki standardowej.

W dokumentacji historycznej termin „znak” był często używany zamiast „bajt” dla ciągów C, co prowadzi wielu do przekonania, że ​​te funkcje jakoś nie działają dla UTF-8 . W rzeczywistości wszystkie długości są definiowane jako wyrażone w bajtach i jest to prawdą we wszystkich implementacjach, a te funkcje działają równie dobrze z UTF-8, jak z kodowaniem jednobajtowym. Dokumentacja BSD została poprawiona, aby było to jasne, ale dokumentacja POSIX, Linux i Windows nadal używa "znaku" w wielu miejscach, gdzie "bajt" lub "wchar_t" jest poprawnym terminem.

Funkcje do obsługi buforów pamięci mogą przetwarzać sekwencje bajtów, które zawierają bajt null jako część danych. Nazwy tych funkcji zwykle zaczynają się od mem, jako przeciwieństwo strprzedrostka.

Stałe i typy

Nazwa Uwagi
NULL Rozwijanie makra do stałej wskaźnika zerowego ; to jest stała reprezentująca wartość wskaźnika, który jest gwarantowany nie być prawidłowy adres obiektu w pamięci.
wchar_t Typ używany dla jednostki kodu w szerokich ciągach, zwykle 16-bitowa lub 32-bitowa wartość bez znaku. Dla tych jednostek kodu nie określono konkretnej interpretacji; standard C wymaga jedynie, aby wchar_t był wystarczająco szeroki, aby pomieścić najszerszy zestaw znaków spośród obsługiwanych ustawień regionalnych systemu . Teoretycznie wchar_t może mieć taki sam rozmiar jak char , a zatem nie może przechowywać jednostek kodu UTF-32 lub UTF-16 .
wint_t Typ Integer, który może przechowywać dowolną wartość wchar_t, a także wartość makra WEOF. Ten typ jest niezmienny w przypadku promocji integralnych. Zwykle 32-bitowa wartość ze znakiem.
mbstate_t Zawiera wszystkie informacje o stanie konwersji wymagane od jednego wywołania funkcji do drugiego.

Funkcje


Ciąg bajtów
Szeroki
sznurek
Opis
String
manipulacji
strcpy wcscpy Kopiuje jeden ciąg do drugiego
strncpy wcsncpy Zapisuje dokładnie n bajtów, kopiując ze źródła lub dodając wartości null
strcat wcscat Dołącza jeden ciąg do drugiego
strncat wcsncat Dołącza nie więcej niż n bajtów z jednego ciągu do drugiego
strxfrm wcsxfrm Przekształca napis zgodnie z aktualnymi ustawieniami regionalnymi

Badanie sznurkowe
strlen wcslen Zwraca długość ciągu
strcmp wcscmp Porównuje dwa ciągi ( porównanie trójdrożne )
strncmp wcsncmp Porównuje określoną liczbę bajtów w dwóch ciągach
strcoll wcscoll Porównuje dwa ciągi znaków zgodnie z bieżącymi ustawieniami lokalnymi
strchr wcschr Znajduje pierwsze wystąpienie bajtu w łańcuchu
strrchr wcsrchr Znajduje ostatnie wystąpienie bajtu w ciągu
strspn wcsspn Zwraca liczbę początkowych bajtów w ciągu, które znajdują się w drugim ciągu
strcspn wcscspn Zwraca liczbę początkowych bajtów w ciągu, których nie ma w drugim ciągu
strpbrk wcspbrk Znajduje w łańcuchu pierwsze wystąpienie bajtu w zestawie
strstr wcsstr Znajduje pierwsze wystąpienie podciągu w ciągu
strtok wcstok Dzieli ciąg na tokeny
Różnorodny strerror Nie dotyczy Zwraca ciąg znaków zawierający wiadomość pochodzącą z kodu błędu

Manipulacja pamięcią
memset wmemset Wypełnia bufor powtarzającym się bajtem
memcpy wmemcpy Kopiuje jeden bufor do drugiego
memmove wmemmove Kopiuje jeden bufor do drugiego, prawdopodobnie zachodząc na siebie, bufor
memcmp wmemcmp Porównuje dwa bufory (porównanie trójstronne)
memchr wmemchr Znajduje pierwsze wystąpienie bajtu w buforze
  1. ^ Dla funkcji szerokich łańcuchów zastąpwchar_t"bajt" w opisie

Funkcje wielobajtowe

Nazwa Opis
mblen Zwraca liczbę bajtów w następnym znaku wielobajtowym
mbtowc Konwertuje następny znak wielobajtowy na znak szeroki
wctomb Konwertuje znak szeroki na jego wielobajtową reprezentację
mbstowcs Konwertuje ciąg wielobajtowy na ciąg szeroki
wcstombs Konwertuje szeroki ciąg na ciąg wielobajtowy
btowc Konwertuj znak jednobajtowy na znak szeroki, jeśli to możliwe
wctob Konwertuj szeroki znak na znak jednobajtowy, jeśli to możliwe
mbsinit Sprawdza, czy obiekt stanu reprezentuje stan początkowy
mbrlen Zwraca liczbę bajtów w następnym znaku wielobajtowym w danym stanie
mbrtowc Konwertuje następny znak wielobajtowy na znak szeroki, podany stan
wcrtomb Konwertuje znak szeroki na jego wielobajtową reprezentację, podany stan
mbsrtowcs Konwertuje ciąg wielobajtowy na ciąg szeroki, podany stan
wcsrtombs Konwertuje szeroki ciąg na ciąg wielobajtowy w podanym stanie

Wszystkie te funkcje przyjmują wskaźnik do mbstate_tobiekt, który musi zachować wywołujący. Pierwotnie było to przeznaczone do śledzenia stanów zmiany wmbkodowania, ale nowoczesne, takie jak UTF-8, nie potrzebują tego. Jednak funkcje te zostały zaprojektowane przy założeniu, żetoaletakodowanie nie jest kodowaniem o zmiennej szerokości i dlatego jest zaprojektowane tak, aby radzić sobie z dokładnie jednymwchar_tna raz, przekazując go przez wartość, a nie za pomocą wskaźnika ciągu. Ponieważ UTF-16 jest kodowaniem o zmiennej szerokości,mbstate_t został ponownie użyty do śledzenia par zastępczych w szerokim kodowaniu, chociaż dzwoniący nadal musi wykrywać i dzwonić mbtowc dwa razy dla jednego znaku.

Konwersje numeryczne


Ciąg bajtów
Szeroki
sznurek
Opis
atof Nie dotyczy konwertuje ciąg na wartość zmiennoprzecinkową ('atof' oznacza 'ASCII na float')
atoi
atol
atoll
Nie dotyczy konwertuje ciąg na liczbę całkowitą ( C99 ) ('atoi' oznacza 'ASCII na liczbę całkowitą')
strtof( C99 )
strtod
strtold( C99 )
wcstof( C99 )
wcstod
wcstold( C99 )
konwertuje ciąg na wartość zmiennoprzecinkową
strtol
strtoll
wcstol
wcstoll
konwertuje łańcuch na liczbę całkowitą ze znakiem
strtoul
strtoull
wcstoul
wcstoull
konwertuje łańcuch na liczbę całkowitą bez znaku
  1. ^ Tutaj ciąg odnosi się do ciągu bajtów lub szerokiego ciągu

Biblioteka standardowa C zawiera kilka funkcji do konwersji numerycznych. Funkcje, które zajmują się ciągami bajtów są zdefiniowane w stdlib.hnagłówku ( cstdlibnagłówek w C++). Funkcje, które zajmują się szerokimi ciągami są zdefiniowane w wchar.hnagłówku ( cwcharnagłówek w C++).

Te strtoxxxfunkcje nie są const-poprawne , ponieważ akceptują constwskaźnik ciąg i powrócić niebędącą constwskaźnik wewnątrz łańcucha.

Ponadto, od czasu zmiany normatywnej nr 1 (C95), atoxxfunkcje są uważane za objęte strtoxxxfunkcjami, z tego powodu ani C95, ani żaden późniejszy standard nie zawiera wersji tych funkcji o szerokim zakresie znaków. Argumentem przeciwko atoxxjest to, że nie rozróżniają między błędem a 0.

Popularne rozszerzenia

Nazwa Platforma Opis
bzero POSIX , BSD Wypełnia bufor zerowymi bajtami, przestarzałe przez memset
memccpy SVID , POSIX kopiuje do określonej liczby bajtów między dwoma obszarami pamięci, które nie mogą na siebie nachodzić, zatrzymując się po znalezieniu danego bajtu.
mempcpy GNU ANTYLOPA wariant memcpyzwracania wskaźnika do bajtu następującego po ostatnim zapisanym bajcie
strcasecmp POSIX, BSD wersje bez rozróżniania wielkości liter strcmp
strcat_s Okna wariant, strcatktóry sprawdza rozmiar bufora docelowego przed kopiowaniem
strcpy_s Okna wariant, strcpyktóry sprawdza rozmiar bufora docelowego przed kopiowaniem
strdup POSIX przydziela i duplikuje ciąg
strerror_r POSIX 1, GNU wariant strerrortego jest bezpieczny wątkowo. Wersja GNU jest niekompatybilna z wersją POSIX.
stricmp Okna wersje bez rozróżniania wielkości liter strcmp
strlcpy BSD, Solaris wariant, strcpyktóry obcina wynik, aby zmieścił się w buforze docelowym
strlcat BSD, Solaris wariant, strcatktóry obcina wynik, aby zmieścił się w buforze docelowym
strsignal POSIX:2008 zwraca ciąg reprezentujący kod sygnału . Nie bezpieczny dla wątków.
strtok_r POSIX wariant strtoktego jest bezpieczny wątkowo

Części zamienne

Pomimo ugruntowanej potrzeby wymiany strcati strcpyfunkcji, które nie pozwalają na przepełnienie bufora, nie powstał żaden akceptowany standard. Wynika to częściowo z błędnego przekonania wielu programistów C, że strncati strncpymają pożądane zachowanie; jednak żadna z funkcji nie została do tego zaprojektowana (miały one na celu manipulowanie buforami ciągów znaków o stałym rozmiarze wypełnionymi wartością null, formatem danych rzadziej używanym w nowoczesnym oprogramowaniu), a zachowanie i argumenty są nieintuicyjne i często pisane niepoprawnie nawet przez eksperta programiści.

Najpopularniejszymi zamiennikami są funkcje strlcati strlcpy, które pojawiły się w OpenBSD 2.4 w grudniu 1998 roku. obcięcia i zapewnia rozmiar do utworzenia nowego bufora, który nie zostanie obcięty. Zostały skrytykowane za rzekomą nieefektywność, zachęcanie do używania ciągów C (zamiast jakiejś lepszej alternatywnej formy ciągu) i ukrywanie innych potencjalnych błędów. W związku z tym nie zostały włączone do biblioteki GNU C (używanej przez oprogramowanie w systemie Linux), chociaż są zaimplementowane w bibliotekach C dla OpenBSD, FreeBSD , NetBSD , Solaris , OS X i QNX , a także w alternatywnych bibliotekach C dla Linuksa, takich jak musl wprowadzony w 2011 roku. Brak obsługi biblioteki GNU C nie powstrzymał różnych autorów oprogramowania przed używaniem jej i dołączaniem zamiennika, m.in. SDL , GLib , ffmpeg , rsync , a nawet wewnętrznie w jądrze Linux . Dostępne są implementacje open source dla tych funkcji.

Czasami memcpylub memmovesą używane, ponieważ mogą być bardziej wydajne niż strcpyponieważ nie sprawdzają wielokrotnie NUL (jest to mniej prawdziwe w przypadku nowoczesnych procesorów). Ponieważ potrzebują długości bufora jako parametru, prawidłowe ustawienie tego parametru może zapobiec przepełnieniu bufora.

W ramach cyklu rozwoju zabezpieczeń w 2004 r. firma Microsoft wprowadziła rodzinę „bezpiecznych” funkcji, w tym strcpy_si strcat_s(wraz z wieloma innymi). Funkcje te zostały znormalizowane z pewnymi drobnymi zmianami w ramach opcjonalnego C11 (Załącznik K) zaproponowanego przez ISO/IEC WDTR 24731. Funkcje te wykonują różne kontrole, w tym, czy ciąg nie jest zbyt długi, aby zmieścić się w buforze. Jeśli sprawdzenie się nie powiedzie, wywoływana jest określona przez użytkownika funkcja "obsługa ograniczeń uruchomieniowych", która zwykle przerywa działanie programu. Niektóre funkcje wykonują destrukcyjne operacje przed wywołaniem procedury obsługi ograniczeń w czasie wykonywania; na przykład strcat_sustawia miejsce docelowe na pusty ciąg, co może utrudnić odzyskanie po błędach lub ich debugowanie. Funkcje te spotkały się z sporą krytyką, ponieważ początkowo były implementowane tylko w systemie Windows, a jednocześnie Microsoft Visual C++ zaczął generować komunikaty ostrzegawcze, sugerujące programistom korzystanie z tych funkcji zamiast standardowych. Niektórzy spekulowali, że jest to próba zablokowania programistów na swojej platformie przez Microsoft. Chociaż dostępne są implementacje tych funkcji typu open source, funkcje te nie są obecne w popularnych bibliotekach Unix C. Doświadczenia z tymi funkcjami wykazały znaczne problemy z ich przyjęciem i błędami w użyciu, dlatego proponuje się usunięcie Załącznika K przy następnej rewizji normy C. memset_sSugerowano również użycie jako sposobu na uniknięcie niechcianych optymalizacji kompilatora.

Zobacz też

Uwagi

Bibliografia

Zewnętrzne linki