Zauważ, że nie chodzi o „ścisłe programowanie Unicode” jako takie, ale o pewne praktyczne doświadczenie.
W mojej firmie utworzyliśmy bibliotekę opakowującą wokół biblioteki ICU IBM. Biblioteka opakowująca ma interfejs UTF-8 i konwertuje do UTF-16, gdy konieczne jest wywołanie ICU. W naszym przypadku nie przejmowaliśmy się zbytnio hitami wydajnościowymi. Gdy problemem była wydajność, dostarczyliśmy również interfejsy UTF-16 (używając naszego własnego typu danych).
Aplikacje mogą pozostać w dużej mierze takie, jakie są (przy użyciu znaku), chociaż w niektórych przypadkach muszą być świadome pewnych problemów. Na przykład zamiast strncpy () używamy opakowania, które pozwala uniknąć odcinania sekwencji UTF-8. W naszym przypadku jest to wystarczające, ale można by też rozważyć sprawdzanie łączenia znaków. Mamy też opakowania do zliczania liczby punktów kodowych, liczby grafemów itp.
Podczas łączenia się z innymi systemami czasami musimy dostosować kompozycję postaci, więc możesz potrzebować tam pewnej elastyczności (w zależności od aplikacji).
Nie używamy wchar_t. Korzystanie z ICU pozwala uniknąć nieoczekiwanych problemów z przenośnością (ale nie innych nieoczekiwanych problemów, oczywiście :-).
strncpy
prawidłowo używany jest całkowicie bezpieczny w użyciu z UTF-8.strcpy
(co jest rzeczywiście bezpieczne w użyciu z UTF-8). Ludzie używającystrncpy
prawdopodobnie robią to, ponieważ nie wiedzą, czy bufor docelowy jest wystarczająco duży, więc chcą przekazać maksymalną liczbę bajtów do skopiowania - co może rzeczywiście spowodować powstanie nieprawidłowych sekwencji UTF-8.C99 lub wcześniej
Standard C (C99) zapewnia szerokie znaki i znaki wielobajtowe, ale ponieważ nie ma gwarancji, co te szerokie znaki mogą pomieścić, ich wartość jest nieco ograniczona. Dla danej implementacji zapewniają przydatne wsparcie, ale jeśli Twój kod musi mieć możliwość przemieszczania się między implementacjami, to nie ma wystarczającej gwarancji, że będą przydatne.
W związku z tym podejście zaproponowane przez Hansa van Ecka (polegające na napisaniu otoki wokół biblioteki ICU - International Components for Unicode) jest rozsądne, IMO.
Kodowanie UTF-8 ma wiele zalet, z których jedną jest to, że jeśli nie zepsujesz danych (na przykład skracając je), mogą zostać skopiowane przez funkcje, które nie są w pełni świadome zawiłości UTF-8 kodowanie. To zdecydowanie nie dotyczy
wchar_t
.Pełny Unicode to format 21-bitowy. Oznacza to, że Unicode rezerwuje punkty kodowe od U + 0000 do U + 10FFFF.
Jedną z przydatnych rzeczy w formatach UTF-8, UTF-16 i UTF-32 (gdzie UTF oznacza format transformacji Unicode - patrz Unicode ) jest to, że można konwertować między tymi trzema reprezentacjami bez utraty informacji. Każdy może reprezentować wszystko, co inni mogą reprezentować. Zarówno UTF-8, jak i UTF-16 są formatami wielobajtowymi.
Wiadomo, że UTF-8 jest formatem wielobajtowym, o starannej strukturze, która umożliwia niezawodne znajdowanie początku znaków w ciągu, począwszy od dowolnego punktu ciągu. Znaki jednobajtowe mają ustawiony wysoki bit na zero. Znaki wielobajtowe mają pierwszy znak zaczynający się od jednego ze wzorów bitowych 110, 1110 lub 11110 (dla znaków 2-bajtowych, 3-bajtowych lub 4-bajtowych), a kolejne bajty zawsze zaczynają się od 10. Znaki kontynuacji są zawsze w zakres 0x80 .. 0xBF. Istnieją zasady, zgodnie z którymi znaki UTF-8 muszą być przedstawiane w jak najmniejszym formacie. Jedną z konsekwencji tych reguł jest to, że bajty 0xC0 i 0xC1 (także 0xF5..0xFF) nie mogą występować w prawidłowych danych UTF-8.
U+0000 .. U+007F 1 byte 0xxx xxxx U+0080 .. U+07FF 2 bytes 110x xxxx 10xx xxxx U+0800 .. U+FFFF 3 bytes 1110 xxxx 10xx xxxx 10xx xxxx U+10000 .. U+10FFFF 4 bytes 1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx
Początkowo oczekiwano, że Unicode będzie 16-bitowym zestawem kodu i wszystko będzie pasować do 16-bitowej przestrzeni kodowej. Niestety, rzeczywisty świat jest bardziej złożony i musiał zostać rozszerzony do obecnego 21-bitowego kodowania.
UTF-16 jest zatem pojedynczym kodem jednostki (słowo 16-bitowe) dla „Basic Multilingual Plane”, co oznacza znaki z punktami kodowymi Unicode U + 0000 .. U + FFFF, ale wykorzystuje dwie jednostki (32-bitowe) dla znaków spoza tego zakresu. Tak więc kod, który działa z kodowaniem UTF-16, musi być w stanie obsługiwać kodowanie o zmiennej szerokości, tak jak musi to być UTF-8. Kody znaków podwójnych nazywane są surogatami.
Oczywiście UTF-32 może zakodować dowolny punkt kodu Unicode w pojedynczej jednostce pamięci. Jest wydajna do obliczeń, ale nie do przechowywania.
Więcej informacji można znaleźć na stronach internetowych ICU i Unicode.
C11 i
<uchar.h>
Standard C11 zmienił zasady, ale nie wszystkie implementacje nadążyły za zmianami nawet teraz (połowa 2017 roku). Standard C11 podsumowuje zmiany dotyczące obsługi Unicode jako:
Poniżej przedstawiono minimalny zarys funkcjonalności. Specyfikacja obejmuje:
(Tłumaczenie odsyłaczy:
<stddef.h>
definiujesize_t
,<wchar.h>
definiujembstate_t
i<stdint.h>
definiujeuint_least16_t
iuint_least32_t
.)<uchar.h>
Nagłówek definiuje również minimalny zestaw (uruchamialnych) funkcji konwersji:Istnieją reguły określające, które znaki Unicode mogą być używane w identyfikatorach przy użyciu notacji
\unnnn
lub\U00nnnnnn
. Może być konieczne aktywne aktywowanie obsługi takich znaków w identyfikatorach. Na przykład GCC wymaga,-fextended-identifiers
aby zezwolić na te identyfikatory.Zwróć uwagę, że macOS Sierra (10.12.5), żeby wymienić tylko jedną platformę, nie obsługuje
<uchar.h>
.źródło
wchar_t
trochę tu sprzedajesz i przyjaciół. Te typy są niezbędne, aby umożliwić bibliotece C obsługę tekstu w dowolnym kodowaniu (w tym kodowaniu innym niż Unicode). Bez szerokich typów znaków i funkcji biblioteka C wymagałaby zestawu funkcji obsługujących tekst dla każdego obsługiwanego kodowania: wyobraź sobie, że koi8len, koi8tok, koi8printf tylko dla tekstu zakodowanego KOI-8 i utf8len, utf8tok, utf8printf dla UTF-8 tekst. Zamiast tego, mamy szczęście mieć tylko jeden zestaw tych funkcji (nie licząc oryginalne ASCII)wcslen
,wcstok
orazwprintf
.mbstowcs
i przyjaciół), aby przekonwertować dowolne obsługiwane kodowanie nawchar_t
. Powchar_t
sformatowaniu programista może używać pojedynczego zestawu funkcji obsługi szerokiego tekstu, które zapewnia biblioteka C. Dobra implementacja biblioteki C obsługuje praktycznie każde kodowanie, którego większość programistów kiedykolwiek będzie potrzebować (na jednym z moich systemów mam dostęp do 221 unikalnych kodowań).wchar_t
była wystarczająco szeroka, aby pomieścić dowolny znak obsługiwany przez implementację. Oznacza to (z prawdopodobnie jednym godnym uwagi wyjątkiem) większość implementacji zapewni, że są one na tyle szerokie, że program, który używawchar_t
, poradzi sobie z każdym kodowaniem obsługiwanym przez system (Microsoftwchar_t
ma tylko 16-bitową szerokość, co oznacza, że ich implementacja nie obsługuje w pełni wszystkich kodowań, przede wszystkim różne kodowania UTF, ale ich jest wyjątkiem, a nie regułą).To często zadawane pytania zawiera wiele informacji. Pomiędzy tą stroną a tym artykułem Joela Spolsky'ego , będziesz miał dobry początek.
Jeden wniosek, do którego doszedłem po drodze:
wchar_t
to 16 bitów w systemie Windows, ale niekoniecznie 16 bitów na innych platformach. Myślę, że jest to zło konieczne w systemie Windows, ale prawdopodobnie można go uniknąć gdzie indziej. Powodem, dla którego jest to ważne w systemie Windows, jest to, że potrzebujesz go do używania plików, które mają w nazwie znaki inne niż ASCII (wraz z wersją funkcji W).Zwróć uwagę, że interfejsy API systemu Windows, które przyjmują
wchar_t
ciągi, oczekują kodowania UTF-16. Zauważ również, że różni się to od UCS-2. Zwróć uwagę na pary zastępcze. Ta strona testowa zawiera pouczające testy.Jeśli jesteś programowania na Windows, nie można używać
fopen()
,fread()
,fwrite()
, itd., Ponieważ tylko braćchar *
i nie rozumieją kodowanie UTF-8. Sprawia, że przenoszenie jest bolesne.źródło
f*
i znajomych z pracychar *
na każdej platformie, ponieważ standard mówi tak - użyjwcs*
zamiast do wchar_t.Aby wykonać ścisłe programowanie w Unicode:
strlen
,strcpy
... ale ich odpowiedniki WideStringwstrlen
,wsstrcpy
...)Wielobajtowe sekwencje znaków to kodowanie poprzedzające kodowanie UTF-16 (to używane normalnie z
wchar_t
) i wydaje mi się, że jest to raczej tylko Windows.Nigdy o tym nie słyszałem
wint_t
.źródło
Najważniejsze jest, aby zawsze wyraźnie odróżniać tekst od danych binarnych . Starają się podążać za model Python 3.x
str
vs.bytes
lub SQLTEXT
vs.BLOB
.Niestety, C myli problem, używając
char
zarówno dla „znaku ASCII”, jak iint_least8_t
. Będziesz chciał zrobić coś takiego:typedef char UTF8; // for code units of UTF-8 strings typedef unsigned char BYTE; // for binary data
Możesz również potrzebować czcionek typu dla jednostek kodu UTF-16 i UTF-32, ale jest to bardziej skomplikowane, ponieważ kodowanie
wchar_t
nie jest zdefiniowane. Będziesz potrzebował tylko preprocesora#if
. Niektóre przydatne makra w C i C ++ 0x to:__STDC_UTF_16__
- Jeśli zdefiniowano, typ_Char16_t
istnieje i to UTF-16.__STDC_UTF_32__
- Jeśli zdefiniowano, typ_Char32_t
istnieje i to UTF-32.__STDC_ISO_10646__
- Jeśli zdefiniowano, towchar_t
jest to UTF-32._WIN32
- W systemie Windowswchar_t
jest UTF-16, mimo że łamie to standard.WCHAR_MAX
- Może być używany do określenia rozmiaruwchar_t
, ale nie do określenia , czy system operacyjny używa go do reprezentowania Unicode.Zobacz też:
Nie. UTF-8 to doskonale poprawne kodowanie Unicode, które używa
char*
ciągów. Ma tę zaletę, że jeśli twój program jest przezroczysty dla bajtów spoza ASCII (np. Konwerter kończący linię, który działa na innych znakach\r
i\n
przepuszcza je bez zmian), nie będziesz musiał dokonywać żadnych zmian!Jeśli wybierzesz UTF-8, będziesz musiał zmienić wszystkie założenia, że
char
= znak (np. Nie wywołujtoupper
w pętli) lubchar
= kolumna ekranu (np. Do zawijania tekstu).Jeśli zdecydujesz się na UTF-32, będziesz miał prostotę znaków o stałej szerokości (ale nie grafemów o stałej szerokości , ale będziesz musiał zmienić typ wszystkich swoich ciągów).
Jeśli wybierzesz UTF-16, będziesz musiał odrzucić zarówno założenie o stałej szerokości znaków, jak i założenie 8-bitowych jednostek kodu, co sprawia, że jest to najtrudniejsza ścieżka aktualizacji z kodowania jednobajtowego.
Zalecałbym aktywne unikanie,
wchar_t
ponieważ nie jest to wieloplatformowe: czasami jest to UTF-32, czasami jest to UTF-16, a czasami jest to kodowanie wschodnioazjatyckie poprzedzające Unicode. Polecam używanietypedefs
Co ważniejsze, unikaj
TCHAR
.źródło
char *
mogą mieć problemy, jeśli zostaną przekazane jakoconst char *
ostatnie, o których pamiętam (ale nie jestem dokładny w tym i które funkcje, więc weź to ze szczyptą soli). To, że jest to bardziej skomplikowane w przypadku innych języków, nie oznacza, że jest to zły projekt.Nie ufałbym żadnej standardowej implementacji biblioteki. Po prostu rzuć własne typy Unicode.
#include <windows.h> typedef unsigned char utf8_t; typedef unsigned short utf16_t; typedef unsigned long utf32_t; int main ( int argc, char *argv[] ) { int msgBoxId; utf16_t lpText[] = { 0x03B1, 0x0009, 0x03B2, 0x0009, 0x03B3, 0x0009, 0x03B4, 0x0000 }; utf16_t lpCaption[] = L"Greek Characters"; unsigned int uType = MB_OK; msgBoxId = MessageBoxW( NULL, lpText, lpCaption, uType ); return 0; }
źródło
Zasadniczo chcesz traktować łańcuchy w pamięci jako
wchar_t
tablice zamiast znaków . Kiedy wykonujesz jakiekolwiek operacje we / wy (takie jak czytanie / zapisywanie plików), możesz kodować / dekodować przy użyciu UTF-8 (jest to prawdopodobnie najpopularniejsze kodowanie), które jest wystarczająco proste do zaimplementowania. Po prostu wygoogluj RFC. Więc nic w pamięci nie powinno być wielobajtowe. Jedenwchar_t
reprezentuje jedną postać. Jednak kiedy przychodzi do serializacji, wtedy musisz zakodować do czegoś takiego jak UTF-8, gdzie niektóre znaki są reprezentowane przez wiele bajtów.Będziesz także musiał napisać nowe wersje
strcmp
itp. Dla szerokich ciągów znaków, ale nie jest to duży problem. Największym problemem będzie współdziałanie z bibliotekami / istniejącym kodem, który akceptuje tylko tablice char.A jeśli chodzi o
sizeof(wchar_t)
(będziesz potrzebować 4 bajtów, jeśli chcesz to zrobić dobrze), zawsze możesz przedefiniować go do większego rozmiaru za pomocątypedef
/macro
hacks, jeśli chcesz.źródło
Z tego co wiem, wchar_t jest zależny od implementacji (jak widać w tym artykule wiki ). I to nie jest Unicode.
źródło