Obsługa ciągów C - C string handling
Biblioteka standardowa C |
---|
Tematy ogólne |
Różne nagłówki |
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 \0
go 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 ( char
lub 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_t
jest to co najmniej 16 bitów, a więc wszystkie kodowania 16-bitowe, takie jak UCS-2 , mogą być przechowywane. Jeśli wchar_t
jest 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_t
i 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_t
ma 16 bitów. Obcinanie ciągów ze znakami o zmiennej długości przy użyciu funkcji takich jak strncpy
moż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 char
kompilatory 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 \xNN
sekwencji dla każdego bajtu UTF-8 i/lub \uNNNN
dla 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.h
nagłówku ( cstring
w C++), podczas gdy funkcje operujące na ciągach o szerokości C są deklarowane w wchar.h
nagłówku ( cwchar
w 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.h
są 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 const
wskaźnik w łańcuchu i zwracając const
niewskaź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 str
przedrostka.
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 | |
|
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 |
|
Biblioteka standardowa C zawiera kilka funkcji do konwersji numerycznych. Funkcje, które zajmują się ciągami bajtów są zdefiniowane w stdlib.h
nagłówku ( cstdlib
nagłówek w C++). Funkcje, które zajmują się szerokimi ciągami są zdefiniowane w wchar.h
nagłówku ( cwchar
nagłówek w C++).
Te strtoxxx
funkcje nie są const-poprawne , ponieważ akceptują const
wskaźnik ciąg i powrócić niebędącą const
wskaźnik wewnątrz łańcucha.
Ponadto, od czasu zmiany normatywnej nr 1 (C95), atoxx
funkcje są uważane za objęte strtoxxx
funkcjami, z tego powodu ani C95, ani żaden późniejszy standard nie zawiera wersji tych funkcji o szerokim zakresie znaków. Argumentem przeciwko atoxx
jest 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 memcpy zwracania 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, strcat który sprawdza rozmiar bufora docelowego przed kopiowaniem
|
strcpy_s
|
Okna | wariant, strcpy który sprawdza rozmiar bufora docelowego przed kopiowaniem
|
strdup
|
POSIX | przydziela i duplikuje ciąg |
strerror_r
|
POSIX 1, GNU | wariant strerror tego 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, strcpy który obcina wynik, aby zmieścił się w buforze docelowym
|
strlcat
|
BSD, Solaris | wariant, strcat któ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 strtok tego jest bezpieczny wątkowo
|
Części zamienne
Pomimo ugruntowanej potrzeby wymiany strcat
i strcpy
funkcji, 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 strncat
i strncpy
mają 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 strlcat
i 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 memcpy
lub memmove
są używane, ponieważ mogą być bardziej wydajne niż strcpy
ponieważ 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_s
i 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_s
ustawia 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_s
Sugerowano również użycie jako sposobu na uniknięcie niechcianych optymalizacji kompilatora.
Zobacz też
- Składnia C § Strings – składnia kodu źródłowego, w tym sekwencje specjalne odwrotnego ukośnika
- Funkcje ciągów
Uwagi
Bibliografia
Zewnętrzne linki
- Szybka memcpy w języku C , wiele przykładów kodowania w języku C dla różnych typów architektur instrukcji procesora