C programlama: Unicode için nasıl programlanır?


83

Katı Unicode programlama yapmak için hangi önkoşullar gereklidir?

Bu benim kod kullanmaması gerektiğini ima mu charyerde türleri ve bu fonksiyonlar ile başa anlamına kullanılması gereken wint_tve wchar_t?

Ve bu senaryoda çok baytlı karakter dizilerinin oynadığı rol nedir?

Yanıtlar:


21

Bunun kendi başına "katı unicode programlama" ile ilgili olmadığını, ancak bazı pratik deneyimlerle ilgili olduğunu unutmayın.

Şirketimde yaptığımız şey, IBM'in ICU kitaplığı etrafında bir sarmalayıcı kitaplığı oluşturmaktı. Sarmalayıcı kitaplığı bir UTF-8 arayüzüne sahiptir ve ICU'yu çağırmak gerektiğinde UTF-16'ya dönüştürür. Bizim durumumuzda, performans hitleri konusunda çok fazla endişelenmedik. Performans bir sorun olduğunda, UTF-16 arayüzleri de sağladık (kendi veri türümüzü kullanarak).

Uygulamalar büyük ölçüde olduğu gibi kalabilir (char kullanarak), ancak bazı durumlarda belirli sorunların farkında olmaları gerekir. Örneğin strncpy () yerine UTF-8 dizilerini kesmekten kaçınan bir sarmalayıcı kullanıyoruz. Bizim durumumuzda bu yeterlidir, ancak karakterleri birleştirmek için kontroller de düşünülebilir. Ayrıca kod noktalarının sayısını, grafiklerin sayısını vb. Saymak için sarmalayıcılarımız var.

Diğer sistemlerle arayüz oluştururken, bazen özel karakter kompozisyonu yapmamız gerekir, bu nedenle burada biraz esnekliğe ihtiyacınız olabilir (uygulamanıza bağlı olarak).

Wchar_t kullanmıyoruz. YBÜ kullanmak, taşınabilirlikte beklenmeyen sorunları önler (ancak diğer beklenmedik sorunları elbette ortadan kaldırmaz :-).


2
Geçerli bir UTF-8 bayt dizisi strncpy tarafından asla kesilmez (kesilmez). Geçerli UTF-8 dizileri 0x00 bayt içermeyebilir (tabii ki sonlandırıcı boş bayt hariç).
Dan Molding

8
@Dan Kalıplama: 2 baytlık bir karakter dizisine tek bir Çince karakter (3 bayt olabilir) içeren bir dizeyi strncpy () yaparsanız, geçersiz bir UTF-8 dizisi oluşturursunuz.
Hans van Eck

@Hans van Eck: Eğer paketleyiciniz o tek 3 baytlık Çince karakteri 2 baytlık bir diziye kopyalarsa, o zaman ya onu kesip geçersiz bir sıra oluşturursunuz ya da tanımlanmamış bir davranışınız olur. Açıkçası, etrafta veri kopyalıyorsanız, hedefin yeterince büyük olması gerekir; Söylemeye gerek yok. Demek istediğim, doğru strncpykullanıldığında UTF-8 ile kullanımın tamamen güvenli olduğuydu.
Dan Molding

5
@DanMoulding: Hedef tamponunuzun yeterince büyük olduğunu biliyorsanız , kullanabilirsiniz strcpy(ki bu gerçekten UTF-8 ile kullanmak için güvenlidir). Kullananlar strncpymuhtemelen bunu , hedef arabelleğin yeterince büyük olup olmadığını bilmedikleri için yapıyorlar, bu yüzden kopyalamak için maksimum sayıda bayt geçirmek istiyorlar - ki bu gerçekten geçersiz UTF-8 dizileri oluşturabilir.
Frerich Raabe

42

C99 veya öncesi

C standardı (C99) geniş karakterler ve çok baytlı karakterler sağlar, ancak bu geniş karakterlerin ne tutabileceğine dair bir garanti olmadığından, değerleri bir şekilde sınırlıdır. Belirli bir uygulama için yararlı destek sağlarlar, ancak kodunuzun uygulamalar arasında hareket edebilmesi gerekiyorsa, bunların yararlı olacağına dair yeterli garanti yoktur.

Sonuç olarak, Hans van Eck'in önerdiği yaklaşım (ICU - Unicode için Uluslararası Bileşenler - kütüphane etrafına bir sarmalayıcı yazmaktır) sağlam, IMO.

UTF-8 kodlamasının birçok faydası vardır; bunlardan biri, verilerle uğraşmazsanız (örneğin, keserek), UTF-8'in karmaşıklıklarının tam olarak farkında olmayan işlevler tarafından kopyalanabilir. kodlama. Bu kategorik olarak durum böyle değil wchar_t.

Tam olarak Unicode, 21 bitlik bir formattır. Yani Unicode, U + 0000'den U + 10FFFF'ye kadar kod noktalarını saklar.

UTF-8, UTF-16 ve UTF-32 biçimleriyle ilgili yararlı şeylerden biri (UTF'nin Unicode Dönüştürme Biçimi anlamına gelir - bkz. Unicode ), üç gösterim arasında bilgi kaybı olmadan dönüştürme yapabilmenizdir. Her biri, diğerlerinin temsil edebileceği her şeyi temsil edebilir. Hem UTF-8 hem de UTF-16, çok baytlı biçimlerdir.

UTF-8'in, dizedeki herhangi bir noktadan başlayarak bir dizedeki karakterlerin başlangıcını güvenilir bir şekilde bulmayı mümkün kılan dikkatli bir yapıya sahip çok baytlı bir format olduğu iyi bilinir. Tek baytlık karakterlerin yüksek bit değeri sıfıra ayarlanır. Çok baytlı karakterler, 110, 1110 veya 11110 bit modellerinden biriyle (2 bayt, 3 bayt veya 4 baytlık karakterler için) başlayan ilk karaktere sahiptir ve sonraki baytlar her zaman 10'dan başlar. Devam karakterleri her zaman aralık 0x80 .. 0xBF. UTF-8 karakterlerinin mümkün olan minimum formatta temsil edilmesi gerektiğine dair kurallar vardır. Bu kuralların bir sonucu, 0xC0 ve 0xC1 baytlarının (ayrıca 0xF5..0xFF) geçerli UTF-8 verilerinde görünememesidir.

Başlangıçta, Unicode'un 16 bitlik bir kod kümesi olacağı ve her şeyin 16 bitlik bir kod alanına sığacağı umuluyordu. Ne yazık ki, gerçek dünya daha karmaşık ve mevcut 21 bit kodlamaya genişletilmesi gerekiyordu.

UTF-16 bu nedenle 'Temel Çok Dilli Düzlem' için tek bir birim (16 bitlik kelime) kod setidir, yani Unicode kod noktalarına sahip karakterler U + 0000 .. U + FFFF, ancak iki birim (32 bit) bu aralığın dışındaki karakterler. Bu nedenle, UTF-16 kodlamasıyla çalışan kod, UTF-8'in gerektiği gibi değişken genişlikli kodlamaları işleyebilmelidir. Çift birimli karakterlerin kodlarına vekiller denir.

Suretler, UTF-16'daki eşleştirilmiş kod birimlerinin baş ve son değerleri olarak kullanılmak üzere ayrılmış iki özel Unicode değer aralığından kod noktalarıdır. Yüksek olarak da adlandırılan öncü temsilciler U + D800'den U + DBFF'ye ve sondaki veya düşük temsilciler U + DC00'den U + DFFF'ye kadardır. Karakterleri doğrudan temsil etmediklerinden, yalnızca bir çift olarak temsil ettikleri için bunlara vekiller denir.

Tabii ki UTF-32 tek bir depolama biriminde herhangi bir Unicode kod noktasını kodlayabilir. Hesaplama için etkilidir, ancak depolama için değildir.

ICU ve Unicode web sitelerinde çok daha fazla bilgi bulabilirsiniz .

C11 ve <uchar.h>

C11 standardı kuralları değiştirdi, ancak tüm uygulamalar şu anda bile (2017 ortası) değişiklikleri yakalayamadı. C11 standardı, Unicode desteğine yönelik değişiklikleri şu şekilde özetler:

  • Unicode karakterler ve dizeler ( <uchar.h>) (orijinal olarak ISO / IEC TR 19769: 2004'te belirtilmiştir)

Aşağıda, işlevselliğin çıplak bir taslağı verilmiştir. Spesifikasyon şunları içerir:

6.4.3 Evrensel karakter adları

Sözdizimi
evrensel karakter-adı:
    \u onaltılı-dörtlü
    \U onaltılı-dörtlü onaltılı-dörtlü
onaltılı:
    onaltılık basamaklı onaltılık basamaklı onaltılık basamaklı onaltılık basamak

7.28 Unicode yardımcı programları <uchar.h>

Başlık <uchar.h>, Unicode karakterlerini işlemek için türleri ve işlevleri bildirir.

Bildirilen türler mbstate_t(7.29.1'de açıklanmıştır) ve size_t( 7.19'da açıklanmıştır);

16 bitlik karakterler için kullanılan işaretsiz bir tamsayı türüdür ve aynı türdür uint_least16_t(7.20.1.2'de açıklanmıştır); ve

32 bitlik karakterler için kullanılan işaretsiz bir tamsayı türüdür ve aynı türdür uint_least32_t(ayrıca 7.20.1.2'de açıklanmıştır).

(Çapraz başvurular çevrilmesi: <stddef.h>tanımlarınızı size_t, <wchar.h>tanımlarınızı mbstate_tve <stdint.h>tanımlarınızı uint_least16_tve uint_least32_t.) <uchar.h>Başlık da (yeniden başlatılabilir) dönüştürme işlevleri en az sayıda tanımlar:

  • mbrtoc16()
  • c16rtomb()
  • mbrtoc32()
  • c32rtomb()

\unnnnVeya \U00nnnnnngösterimleri kullanılarak tanımlayıcılarda hangi Unicode karakterlerinin kullanılabileceği hakkında kurallar vardır . Tanımlayıcılarda bu tür karakterlerin desteğini aktif olarak etkinleştirmeniz gerekebilir. Örneğin, GCC -fextended-identifiers, tanımlayıcılarda bunlara izin vermeyi gerektirir .

MacOS Sierra'nın (10.12.5) tek bir platforma ad vermek için desteklemediğini unutmayın <uchar.h>.


3
Sanırım burada satıyorsun wchar_tve arkadaşlar biraz eksik. Bu türler, C kitaplığının herhangi bir kodlamada (Unicode olmayan kodlamalar dahil) metni işlemesine izin vermek için gereklidir . Geniş karakter türleri ve işlevleri olmadan, C kitaplığı desteklenen her kodlama için bir dizi metin işleme işlevi gerektirir : Yalnızca KOI-8 kodlanmış metin için koi8len, koi8tok, koi8printf ve UTF-8 için utf8len, utf8tok, utf8printf olduğunu hayal edin. Metin. Bunun yerine, sadece için şanslı bir bu fonksiyonların (orijinal ASCII olanları hariç) kümesini: wcslen, wcstok, ve wprintf.
Dan Molding

1
Bir programcının yapması gereken tek şey, mbstowcsdesteklenen herhangi bir kodlamayı dönüştürmek için C kitaplığı karakter dönüştürme işlevlerini ( ve arkadaşları) kullanmaktır wchar_t. Bir kez wchar_tformatında, programcı C kütüphanesi sağlar geniş metin işleme fonksiyonlarının tek bir set kullanabilirsiniz. İyi bir C kitaplığı uygulaması, çoğu programcının ihtiyaç duyacağı hemen hemen tüm kodlamaları destekleyecektir (sistemlerimden birinde 221 benzersiz kodlamaya erişimim var).
Dan Molding

Yararlı olacak kadar geniş olup olmayacaklarına gelince: standart, uygulama wchar_ttarafından desteklenen herhangi bir karakteri içerecek kadar geniş bir uygulama gerektirmektedir . Bu, (muhtemelen dikkate değer bir istisna dışında) çoğu uygulamanın wchar_t, sistem tarafından desteklenen herhangi bir kodlamayı işleyebilecek bir programın yeterince geniş olmasını garanti edeceği wchar_tanlamına gelir (Microsoft'un genişliği yalnızca 16 bittir, bu da uygulamalarının tüm kodlamaları tam olarak desteklemediği anlamına gelir. en önemlisi çeşitli UTF kodlamalarıdır, ancak onlarınki kural değil istisnadır).
Dan Molding

11

Bu SSS , zengin bir bilgidir. Bu sayfa ve Joel Spolsky'nin bu makalesi arasında iyi bir başlangıç ​​yapacaksınız.

Yol boyunca vardığım bir sonuç:

  • wchar_tWindows'ta 16 bittir, ancak diğer platformlarda 16 bit olması gerekmez. Bunun Windows için gerekli bir kötülük olduğunu düşünüyorum, ancak muhtemelen başka yerlerde önlenebilir. Windows'ta önemli olmasının nedeni, adında ASCII olmayan karakterler (işlevlerin W sürümü ile birlikte) içeren dosyaları kullanmanız gerektiğidir.

  • wchar_tDizeleri alan Windows API'lerinin UTF-16 kodlaması beklediğini unutmayın . Bunun UCS-2'den farklı olduğuna da dikkat edin. Vekil çiftlerini not edin. Bu test sayfasında aydınlatıcı testler var.

  • Windows üzerinde olduğunuz programlama, kullanmak yapamıyorsanız fopen(), fread(), fwrite()vb onlar sadece almak beri char *ve UTF-8 kodlaması anlamıyorum. Taşınabilirliği acı verici hale getirir.


Not stdio o f*ile ve arkadaşlar eser char *üzerindeki her platformda standart öyle diyor çünkü - kullanımını wcs*yerine wchar_t için.
kedi

7

Katı Unicode programlama yapmak için:

  • Sadece vardır dize API'leri kullanmak Unicode farkında ( DEĞİL strlen , strcpy... ama onların WideString meslektaşları wstrlen, wsstrcpy...)
  • Bir metin bloğu ile uğraşırken, Unicode karakterlerinin (utf-7, utf-8, utf-16, ucs-2, ...) kayıpsız depolanmasına izin veren bir kodlama kullanın.
  • İşletim sistemi varsayılan karakter setinizin Unicode uyumlu olup olmadığını kontrol edin (örn: utf-8)
  • Unicode uyumlu yazı tiplerini kullanın (örneğin arial_unicode)

Çok baytlı karakter dizileri, UTF-16 kodlamasını (normalde birlikte kullanılan wchar_t) önceden tarihlendiren bir kodlamadır ve bana öyle geliyor ki, yalnızca Windows için geçerli.

Hiç duymadım wint_t.


wint_t, wchar_t gibi <wchar.h> içinde tanımlanan bir türdür. Geniş karakterlerde int 'karakter' ile aynı role sahiptir; herhangi bir geniş karakter değerini veya WEOF'u tutabilir.
Jonathan Leffler

3

En önemli şey, metin ve ikili veriler arasında her zaman net bir ayrım yapmaktır . Python 3.x strvebytes SQL TEXTvs. modelini takip etmeye çalışın BLOB.

Ne yazık ki, C charhem "ASCII karakteri" hem de int_least8_t. Şunun gibi bir şey yapmak isteyeceksiniz:

UTF-16 ve UTF-32 kod birimleri için typedef'ler isteyebilirsiniz, ancak kodlaması wchar_ttanımlanmadığı için bu daha karmaşıktır . Sadece bir önişlemciye ihtiyacınız olacak #if. C ve C ++ 0x'deki bazı yararlı makrolar şunlardır:

  • __STDC_UTF_16__- Tanımlanmışsa, tür _Char16_tmevcuttur ve UTF-16'dır.
  • __STDC_UTF_32__- Tanımlanmışsa, tür _Char32_tmevcuttur ve UTF-32'dir.
  • __STDC_ISO_10646__- Tanımlanmışsa, wchar_tUTF-32'dir.
  • _WIN32- Windows'ta, wchar_tstandardı aşsa da UTF-16'dır.
  • WCHAR_MAX- Boyutunu belirlemek için kullanılabilir wchar_t, ancak işletim sisteminin bunu Unicode'u temsil etmek için kullanıp kullanmadığını belirlemek için kullanılamaz .

Bu, kodumun hiçbir yerde char türlerini kullanmaması gerektiği ve wint_t ve wchar_t ile başa çıkabilecek işlevlerin kullanılması gerektiği anlamına mı geliyor?

Ayrıca bakınız:

Hayır. UTF-8, char*dizeleri kullanan mükemmel bir Unicode kodlamadır . Programınız ASCII olmayan baytlara karşı şeffafsa (örneğin, diğer karakterlere etki eden \rve \ndiğer karakterlerden değişmeden geçen bir satır sonlandırma dönüştürücü ), hiçbir değişiklik yapmanız gerekmemesi avantajına sahiptir !

UTF-8 ile giderseniz, char= karakter (örneğin, toupperbir döngüde çağırma ) veya char= ekran sütunu (örneğin, metin kaydırma için) olan tüm varsayımları değiştirmeniz gerekir .

UTF-32 ile giderseniz, sabit genişlikli karakterlerin basitliğine sahip olacaksınız (ancak sabit genişlikli grafikler değil , ancak tüm dizelerinizin türünü değiştirmeniz gerekecektir).

UTF-16 ile giderseniz sabit genişlikli karakterlerin varsayımını hem atmak gerekecek ve bu tek baytlık kodlamaları en zor yükseltme yolu yapar 8 bitlik kod birimlerinin varsayımını.

Çapraz platform olmadığı için aktif olarak kaçınmanızı öneririm wchar_t: Bazen UTF-32, bazen UTF-16 ve bazen de Unicode öncesi Doğu Asya kodlaması. Kullanmanızı tavsiye ederimtypedefs

Daha da önemlisi, kaçınınTCHAR .


Bunun talihsiz olduğunu sanmıyorum - karakter bir int. Bu bir avantaj. Tek kullanımlık karakter sabitlerini kullanmak akla geliyor. Ve a alan işlevler, son hatırladığım char *bir süre geçerse sorun yaşayabilir const char *(ama bu konuda belirsizim ve bu yüzden bir tutam tuzla alın) Diğer dillerde daha karmaşık olması, kötü bir tasarım olduğu anlamına gelmez.
Pryftan

2

Herhangi bir standart kütüphane uygulamasına güvenmem. Kendi unicode türlerinizi döndürün.


2

Temelde bellekteki dizelerle wchar_tchar yerine diziler olarak ilgilenmek istiyorsunuz . Herhangi bir tür G / Ç yaptığınızda (dosyaları okumak / yazmak gibi), uygulamak için yeterince basit olan UTF-8 (bu muhtemelen en yaygın kodlamadır) kullanarak kodlayabilir / kodunu çözebilirsiniz. Yalnızca RFC'leri google. Yani bellek içi hiçbir şey çok baytlı olmamalıdır. Biri wchar_tbir karakteri temsil eder. Bununla birlikte, serileştirmeye geldiğinizde, bazı karakterlerin birden çok baytla temsil edildiği UTF-8 gibi bir şeye kodlamanız gerekir.

Ayrıca strcmpgeniş karakter dizileri için yeni sürümler yazmanız gerekecek , ancak bu büyük bir sorun değil. En büyük sorun, yalnızca char dizilerini kabul eden kütüphaneler / var olan kod ile birlikte çalışmak olacaktır.

Ve söz konusu olduğunda sizeof(wchar_t)(doğru yapmak istiyorsanız 4 bayta ihtiyacınız olacak), gerekirse typedef/ macrohacks ile her zaman daha büyük bir boyuta yeniden tanımlayabilirsiniz .


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.