Pertanyaan Pemrograman C: Bagaimana program untuk Unicode?


Prasyarat apa yang diperlukan untuk melakukan pemrograman Unicode yang ketat?

Apakah ini menyiratkan bahwa kode saya tidak boleh digunakan char ketik di mana saja dan fungsi-fungsi itu perlu digunakan yang dapat ditangani wint_t dan wchar_t?

Dan apa peran yang dimainkan oleh urutan karakter multibyte dalam skenario ini?


75
2018-02-08 21:22


asal


Jawaban:


Perhatikan bahwa ini bukan tentang "pemrograman unicode yang ketat" per se, tetapi beberapa pengalaman praktis.

Apa yang kami lakukan di perusahaan saya adalah membuat perpustakaan wrapper di sekitar perpustakaan ICU IBM. Perpustakaan wrapper memiliki antarmuka UTF-8 dan mengkonversi ke UTF-16 ketika diperlukan untuk memanggil ICU. Dalam kasus kami, kami tidak terlalu mengkhawatirkan tentang kinerja hits. Ketika kinerja merupakan masalah, kami juga menyediakan antarmuka UTF-16 (menggunakan datatype kami sendiri).

Aplikasi dapat tetap sebagian besar apa adanya (menggunakan arang), meskipun dalam beberapa kasus mereka perlu menyadari isu-isu tertentu. Sebagai contoh, alih-alih strncpy () kami menggunakan pembungkus yang menghindari pemotongan sekuens UTF-8. Dalam kasus kami, ini sudah cukup, tetapi bisa juga mempertimbangkan pemeriksaan untuk menggabungkan karakter. Kami juga memiliki pembungkus untuk menghitung jumlah codepoint, jumlah grafik, dll.

Ketika berinteraksi dengan sistem lain, kita terkadang perlu melakukan komposisi karakter khusus, jadi Anda mungkin memerlukan beberapa fleksibilitas di sana (tergantung pada aplikasi Anda).

Kami tidak menggunakan wchar_t. Menggunakan ICU menghindari masalah tak terduga dalam portabilitas (tetapi bukan masalah tak terduga lainnya, tentu saja :-).


20
2018-02-08 22:44



C99 atau sebelumnya

Standar C (C99) menyediakan karakter lebar dan karakter multi-byte, tetapi karena tidak ada jaminan tentang apa yang dapat dimiliki oleh karakter lebar tersebut, nilainya agak terbatas. Untuk implementasi yang diberikan, mereka memberikan dukungan yang berguna, tetapi jika kode Anda harus dapat bergerak di antara implementasi, tidak ada jaminan bahwa mereka akan berguna.

Akibatnya, pendekatan yang disarankan oleh Hans van Eck (yang menulis pembungkus di sekitar ICU - Komponen Internasional untuk Unicode - perpustakaan) adalah suara, IMO.

Enkoding UTF-8 memiliki banyak manfaat, salah satunya adalah bahwa jika Anda tidak mengacaukan data (dengan memotongnya, misalnya), maka itu dapat disalin oleh fungsi-fungsi yang tidak sepenuhnya menyadari seluk-beluk UTF-8 encoding. Ini secara kategoris tidak demikian halnya wchar_t.

Unicode secara penuh adalah format 21-bit. Artinya, poin kode cadangan Unicode dari U + 0000 ke U + 10FFFF.

Salah satu hal yang berguna tentang format UTF-8, UTF-16 dan UTF-32 (di mana UTF adalah singkatan dari Unicode Transformation Format - lihat Unicode) adalah bahwa Anda dapat mengkonversi antara tiga representasi tanpa kehilangan informasi. Masing-masing dapat mewakili apa pun yang dapat diwakilkan oleh orang lain. UTF-8 dan UTF-16 adalah format multi-byte.

UTF-8 dikenal sebagai format multi-byte, dengan struktur yang hati-hati yang memungkinkan untuk menemukan awal karakter dalam string yang dapat diandalkan, mulai dari titik mana pun dalam string. Karakter single-byte memiliki bit-tinggi diatur ke nol. Karakter multi-byte memiliki karakter pertama yang dimulai dengan salah satu dari pola bit 110, 1110 atau 11110 (untuk karakter 2-byte, 3-byte atau 4-byte), dengan byte berikutnya selalu mulai 10. Karakter kelanjutan selalu berada di kisaran 0x80 .. 0xBF. Ada aturan bahwa karakter UTF-8 harus diwakili dalam format minimum yang mungkin. Salah satu konsekuensi dari aturan ini adalah bahwa byte 0xC0 dan 0xC1 (juga 0xF5..0xFF) tidak dapat muncul dalam data UTF-8 yang valid.

 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

Awalnya, diharapkan Unicode akan menjadi kumpulan kode 16-bit dan semuanya akan masuk ke dalam ruang kode 16-bit. Sayangnya, dunia nyata lebih kompleks, dan itu harus diperluas ke pengkodean 21-bit saat ini.

UTF-16 dengan demikian adalah satu unit (16-bit word) kode yang ditetapkan untuk 'Multilingual Plane Basic', yang berarti karakter dengan poin kode Unicode U + 0000 .. U + FFFF, tetapi menggunakan dua unit (32-bit) untuk karakter di luar rentang ini. Jadi, kode yang bekerja dengan pengkodean UTF-16 harus dapat menangani pengkodean lebar variabel, seperti UTF-8 harus. Kode untuk karakter double-unit disebut pengganti.

Pengganti adalah poin kode dari dua rentang khusus nilai Unicode, disediakan untuk digunakan sebagai leading, dan trailing values ​​dari unit kode yang dipasangkan di UTF-16. Leading, juga disebut tinggi, pengganti adalah dari U + D800 ke U + DBFF, dan trailing, atau rendah, pengganti adalah dari U + DC00 ke U + DFFF. Mereka disebut pengganti, karena mereka tidak mewakili karakter secara langsung, tetapi hanya sebagai pasangan.

UTF-32, tentu saja, dapat mengkodekan titik kode Unicode dalam satu unit penyimpanan. Ini efisien untuk perhitungan tetapi tidak untuk penyimpanan.

Anda dapat menemukan lebih banyak informasi di ICU dan situs web Unicode.

C11 dan <uchar.h>

Standar C11 mengubah aturan, tetapi tidak semua implementasi telah menangkap perubahan bahkan sekarang (pertengahan 2017). Standar C11 merangkum perubahan untuk dukungan Unicode sebagai:

  • Karakter dan string Unicode (<uchar.h>) (awalnya ditentukan dalam   ISO / IEC TR 19769: 2004)

Berikut ini adalah garis besar minimal dari fungsionalitas. Spesifikasi termasuk:

6.4.3 Nama karakter universal

Sintaksis
nama-karakter universal:
  \u  hex-quad
  \U  hex-quad hex-quad
hex-quad:
  heksadesimal-digit heksadesimal-digit   heksadesimal-digit heksadesimal-digit

7.28 utilitas Unicode <uchar.h>

Header <uchar.h> menyatakan jenis dan fungsi untuk memanipulasi karakter Unicode.

Tipe yang dideklarasikan adalah mbstate_t (dijelaskan dalam 7.29.1) dan size_t (dijelaskan dalam 7.19);

char16_t

yang merupakan tipe integer unsigned yang digunakan untuk karakter 16-bit dan merupakan tipe yang sama uint_least16_t (dijelaskan dalam 7.20.1.2); dan

char32_t

yang merupakan tipe integer unsigned yang digunakan untuk karakter 32-bit dan merupakan tipe yang sama uint_least32_t (Juga dijelaskan dalam 7.20.1.2).

(Menerjemahkan referensi silang: <stddef.h> mendefinisikan size_t, <wchar.h> mendefinisikan mbstate_t, dan <stdint.h> mendefinisikan uint_least16_t dan uint_least32_t.) Itu <uchar.h> tajuk juga mendefinisikan sekumpulan fungsi konversi minimum (dapat dinyalakan ulang):

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

Ada aturan tentang karakter Unicode yang dapat digunakan dalam pengidentifikasi menggunakan \unnnn atau \U00nnnnnn notasi. Anda mungkin harus aktif mengaktifkan dukungan untuk karakter seperti itu dalam pengenal. Misalnya, GCC membutuhkan -fextended-identifiers untuk memungkinkan ini di pengidentifikasi.

Perhatikan bahwa macOS Sierra (10.12.5), untuk nama tetapi satu platform, tidak mendukung <uchar.h>.


36
2018-02-09 07:00



Ini FAQ adalah kekayaan info. Antara halaman itu dan artikel ini oleh Joel Spolsky, Anda akan memiliki awal yang bagus.

Satu kesimpulan saya datang di sepanjang jalan:

  • wchar_t adalah 16 bit pada Windows, tetapi tidak harus 16 bit pada platform lain. Saya pikir itu adalah kejahatan yang diperlukan pada Windows, tetapi mungkin dapat dihindari di tempat lain. Alasan penting untuk Windows adalah Anda perlu menggunakan file yang memiliki karakter non-ASCII dalam nama (bersama dengan fungsi versi W).

  • Perhatikan bahwa Windows API yang digunakan wchar_t string mengharapkan enkode UTF-16. Perhatikan juga bahwa ini berbeda dari UCS-2. Catat pasangan pengganti. Ini halaman uji memiliki tes yang mencerahkan.

  • Jika Anda memprogram di Windows, Anda tidak dapat menggunakannya fopen(), fread(), fwrite(), dll karena mereka hanya mengambil char * dan tidak mengerti enkode UTF-8. Membuat portabilitas menjadi menyakitkan.


9
2018-02-09 16:34



Untuk melakukan pemrograman Unicode yang ketat:

  • Hanya gunakan API string yang diketahui Unicode (TIDAK  strlen, strcpy, ... tetapi rekan mereka yang lebih luas wstrlen, wsstrcpy, ...)
  • Ketika berhadapan dengan blok teks, gunakan pengkodean yang memungkinkan menyimpan karakter Unicode (utf-7, utf-8, utf-16, ucs-2, ...) tanpa kehilangan.
  • Periksa bahwa set karakter default OS Anda kompatibel dengan Unicode (mis: utf-8)
  • Gunakan font yang kompatibel dengan Unicode (mis. Arial_unicode)

Urutan karakter multi-byte adalah pengkodean yang mengawali pengkodean UTF-16 (yang biasanya digunakan dengan wchar_t) dan menurut saya itu agak hanya-Windows.

Saya belum pernah dengar wint_t.


7
2018-02-08 21:56



Yang paling penting adalah selalu membuat perbedaan yang jelas antara teks dan data biner. Coba ikuti model Python 3.x str vs. bytes atau SQL TEXT vs. BLOB.

Sayangnya, C membingungkan masalah dengan menggunakan char untuk "karakter ASCII" dan int_least8_t. Anda akan ingin melakukan sesuatu seperti:

typedef char UTF8; // for code units of UTF-8 strings
typedef unsigned char BYTE; // for binary data

Anda mungkin menginginkan typedef untuk unit UTF-16 dan UTF-32 juga, tetapi ini lebih rumit karena encoding dari wchar_t tak terdefinisi. Anda harus hanya menjadi preprocessor #ifs. Beberapa macro yang berguna dalam C dan C ++ 0x adalah:

  • __STDC_UTF_16__ - Jika didefinisikan, tipe _Char16_t ada dan UTF-16.
  • __STDC_UTF_32__ - Jika didefinisikan, tipe _Char32_t ada dan UTF-32.
  • __STDC_ISO_10646__ - Jika didefinisikan, maka wchar_t adalah UTF-32.
  • _WIN32 - Di Windows, wchar_t adalah UTF-16, meskipun ini melanggar standar.
  • WCHAR_MAX - Dapat digunakan untuk menentukan ukuran wchar_t, tetapi bukan apakah OS menggunakannya untuk mewakili Unicode.

Apakah ini menyiratkan bahwa kode saya seharusnya   tidak menggunakan tipe char di mana saja dan itu   fungsi harus digunakan yang bisa   berurusan dengan wint_t dan wchar_t?

Lihat juga:

Tidak. UTF-8 adalah pengkodean Unicode yang benar-benar valid yang menggunakan char* string. Ini memiliki keuntungan bahwa jika program Anda transparan untuk byte non-ASCII (misalnya, konverter akhir jalur yang bertindak pada \r dan \n tetapi melewati karakter lain tidak berubah), Anda tidak perlu melakukan perubahan sama sekali!

Jika Anda menggunakan UTF-8, Anda harus mengubah semua asumsi itu char= karakter (misalnya, jangan panggil toupper dalam satu lingkaran) atau char = kolom layar (misalnya, untuk pembungkusan teks).

Jika Anda menggunakan UTF-32, Anda akan memiliki kesederhanaan karakter lebar-tetap (tetapi tidak tetap-lebar grafem, tetapi perlu mengubah jenis semua string Anda).

Jika Anda menggunakan UTF-16, Anda harus membuang asumsi karakter dengan lebar tetap dan asumsi unit kode 8-bit, yang menjadikan ini jalur peningkatan yang paling sulit dari enkode byte tunggal.

Saya akan merekomendasikan secara aktif menghindari  wchar_t karena tidak cross-platform: Kadang-kadang UTF-32, kadang-kadang UTF-16, dan kadang-kadang pengkodean pra-Unicode Asia Timur. Saya akan merekomendasikan menggunakan typedefs 

Bahkan yang lebih penting, menghindari TCHAR.


3
2017-08-18 13:45



Anda pada dasarnya ingin berurusan dengan string dalam memori sebagai array wchar_t bukan char. Ketika Anda melakukan jenis I / O (seperti membaca / menulis file), Anda dapat meng-encode / decode menggunakan UTF-8 (ini mungkin merupakan pengkodean yang paling umum) yang cukup sederhana untuk diterapkan. Hanya google RFC. Jadi di-memori tidak ada yang harus multi-byte. Satu wchar_t mewakili satu karakter. Ketika Anda datang ke serialisasi, saat itulah Anda perlu mengkodekan ke sesuatu seperti UTF-8 di mana beberapa karakter diwakili oleh beberapa byte.

Anda juga harus menulis versi baru dari strcmp dll. Untuk string karakter yang luas, tetapi ini bukan masalah besar. Masalah terbesar akan interop dengan pustaka / kode yang ada yang hanya menerima array arang.

Dan ketika datang ke sizeof (wchar_t) (Anda akan membutuhkan 4 byte jika Anda ingin melakukannya dengan benar), Anda selalu dapat mendefinisikan ulang ke ukuran yang lebih besar dengan typedef / macro hacks jika Anda perlu.


2
2018-02-09 06:40



Saya tidak akan mempercayai penerapan standar perpustakaan. Cukup gulirkan jenis unicode Anda sendiri.

#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;
}

2
2018-03-29 18:45



Dari yang saya tahu, wchar_t tergantung pada implementasi (seperti yang bisa dilihat dari ini artikel wiki). Dan itu bukan unicode.


1
2018-02-09 06:03