Frage C-Programmierung: Wie programmiert man Unicode?


Welche Voraussetzungen sind für eine strikte Unicode-Programmierung erforderlich?

Bedeutet dies, dass mein Code nicht verwendet werden sollte char Typen überall und diese Funktionen müssen verwendet werden, die damit umgehen können wint_t und wchar_t?

Und welche Rolle spielen Multibyte-Zeichenfolgen in diesem Szenario?


75
2018-02-08 21:22


Ursprung


Antworten:


Beachten Sie, dass es hier nicht um "strikte Unicode-Programmierung" geht, sondern um praktische Erfahrung.

In meiner Firma haben wir eine Wrapper-Bibliothek um die ICU-Bibliothek von IBM erstellt. Die Wrapper-Bibliothek hat eine UTF-8-Schnittstelle und konvertiert in UTF-16, wenn ICU aufgerufen werden muss. In unserem Fall haben wir uns nicht allzu viele Sorgen über Leistungseinbußen gemacht. Wenn Leistung ein Problem war, lieferten wir auch UTF-16-Schnittstellen (unter Verwendung unseres eigenen Datentyps).

Anwendungen können weitgehend unverändert bleiben (mit char), obwohl sie sich in bestimmten Fällen bestimmter Probleme bewusst sein müssen. Zum Beispiel verwenden wir statt strncpy () einen Wrapper, der das Abschneiden von UTF-8-Sequenzen vermeidet. In unserem Fall ist dies ausreichend, aber man könnte auch prüfen, ob Zeichen kombiniert werden. Wir haben auch Wrapper zum Zählen der Anzahl der Codepunkte, der Anzahl der Grapheme usw.

Wenn wir uns mit anderen Systemen verbinden, müssen wir manchmal eine benutzerdefinierte Zeichenzusammenstellung vornehmen, so dass Sie dort (abhängig von Ihrer Anwendung) möglicherweise etwas Flexibilität benötigen.

Wir verwenden wchar_t nicht. Die Verwendung von ICU vermeidet unerwartete Probleme bei der Portabilität (aber natürlich nicht andere unerwartete Probleme :-).


20
2018-02-08 22:44



C99 oder früher

Der C-Standard (C99) sieht breite Zeichen und Multi-Byte-Zeichen vor, aber da es keine Garantie dafür gibt, was diese breiten Zeichen halten können, ist ihr Wert etwas begrenzt. Für eine bestimmte Implementierung bieten sie nützliche Unterstützung. Wenn der Code jedoch zwischen den Implementierungen wechseln kann, ist nicht ausreichend gewährleistet, dass sie nützlich sind.

Folglich ist der von Hans van Eck vorgeschlagene Ansatz (das ist ein Wrapper um die ICU - International Components for Unicode - Bibliothek) Sound, IMO.

Die UTF-8-Kodierung hat viele Vorteile, eine davon ist, dass, wenn Sie sich nicht mit den Daten herumschlagen (indem Sie sie beispielsweise abschneiden), sie von Funktionen kopiert werden können, die sich der Komplexität von UTF-8 nicht voll bewusst sind Codierung. Dies ist kategorisch nicht der Fall mit wchar_t.

Unicode in vollem Umfang ist ein 21-Bit-Format. Das heißt, Unicode reserviert Codepunkte von U + 0000 bis U + 10FFFF.

Eines der nützlichen Dinge bei den Formaten UTF-8, UTF-16 und UTF-32 (wobei UTF für Unicode Transformation Format steht - siehe Unicode) ist, dass Sie zwischen den drei Darstellungen ohne Informationsverlust konvertieren können. Jeder kann alles darstellen, was die anderen darstellen können. Sowohl UTF-8 als auch UTF-16 sind Multi-Byte-Formate.

UTF-8 ist bekanntermaßen ein Multi-Byte-Format mit einer sorgfältigen Struktur, die es ermöglicht, den Beginn von Zeichen in einer Zeichenfolge zuverlässig zu finden, beginnend an einem beliebigen Punkt in der Zeichenfolge. Bei Ein-Byte-Zeichen ist das High-Bit auf Null gesetzt. Bei Multibyte-Zeichen beginnt das erste Zeichen mit einem der Bitmuster 110, 1110 oder 11110 (für 2-Byte-, 3-Byte- oder 4-Byte-Zeichen), wobei nachfolgende Bytes immer mit 10 beginnen. Die Fortsetzungszeichen befinden sich immer in der Bereich 0x80 .. 0xBF. Es gibt Regeln, nach denen UTF-8-Zeichen im minimal möglichen Format dargestellt werden müssen. Eine Konsequenz dieser Regeln ist, dass die Bytes 0xC0 und 0xC1 (auch 0xF5..0xFF) nicht in gültigen UTF-8 Daten erscheinen können.

 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

Ursprünglich hatte man gehofft, dass Unicode ein 16-Bit-Code-Set ist und alles in einen 16-Bit-Code-Platz passt. Leider ist die reale Welt komplexer und musste auf die aktuelle 21-Bit-Codierung erweitert werden.

UTF-16 ist somit ein einzelner Einheitscode (16-Bit-Wort) für die 'Basic Multilingual Plane', dh die Zeichen mit Unicode-Codepunkten U + 0000 .. U + FFFF, verwendet aber zwei Einheiten (32-Bit) für Zeichen außerhalb dieses Bereichs. Daher muss Code, der mit der UTF-16-Codierung arbeitet, in der Lage sein, Codierungen mit variabler Breite zu verarbeiten, genau wie UTF-8. Die Codes für die Doppeleinheitszeichen werden als Ersatzzeichen bezeichnet.

Surrogate sind Codepunkte aus zwei speziellen Bereichen von Unicode-Werten, die für die Verwendung als führende und abschließende Werte von gepaarten Codeeinheiten in UTF-16 reserviert sind. Führende, auch hohe Surrogate heißen von U + D800 bis U + DBFF, und nachfolgende oder niedrige Surrogate gehen von U + DC00 nach U + DFFF. Sie werden Surrogate genannt, da sie nicht direkt, sondern nur paarweise darstellen.

UTF-32 kann natürlich jeden Unicode-Codepunkt in einer einzigen Speichereinheit codieren. Es ist effizient für die Berechnung, aber nicht für die Speicherung.

Sie können viel mehr Informationen im finden Intensivstation und Unicode-Websites.

C11 und <uchar.h>

Der C11-Standard hat die Regeln geändert, aber nicht alle Implementierungen haben die Änderungen bereits jetzt (Mitte 2017) aufgeholt. Der C11-Standard fasst die Änderungen für die Unicode-Unterstützung wie folgt zusammen:

  • Unicode-Zeichen und Zeichenfolgen (<uchar.h>) (ursprünglich in   ISO / IEC TR 19769: 2004)

Was folgt, ist ein minimaler Umriss der Funktionalität. Die Spezifikation beinhaltet:

6.4.3 Universelle Charakternamen

Syntax
Universal-Charaktername:
  \u  Hex-Viereck
  \U  Hex-Quad-Hex-Quad
Hex-Quad:
  Hexadezimalziffer Hexadezimalziffer   Hexadezimalziffer Hexadezimalziffer

7.28 Unicode-Dienstprogramme <uchar.h>

Der Header <uchar.h> deklariert Typen und Funktionen zum Manipulieren von Unicode-Zeichen.

Die angegebenen Arten sind mbstate_t (beschrieben in 7.29.1) und size_t (beschrieben in 7.19);

char16_t

Dies ist ein vorzeichenloser Integer-Typ, der für 16-Bit-Zeichen verwendet wird und den gleichen Typ wie uint_least16_t (beschrieben in 7.20.1.2); und

char32_t

Dies ist ein vorzeichenloser Integertyp, der für 32-Bit-Zeichen verwendet wird und den gleichen Typ wie uint_least32_t (auch beschrieben in 7.20.1.2).

(Übersetzen der Querverweise: <stddef.h> definiert size_t, <wchar.h> definiert mbstate_t, und <stdint.h> definiert uint_least16_t und uint_least32_t.) Das <uchar.h> header definiert auch einen minimalen Satz von (neu startbaren) Konvertierungsfunktionen:

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

Es gibt Regeln darüber, welche Unicode-Zeichen in Bezeichnern verwendet werden können \unnnn oder \U00nnnnnn Notationen. Möglicherweise müssen Sie die Unterstützung für solche Zeichen in Bezeichnern aktiv aktivieren. Zum Beispiel erfordert GCC -fextended-identifiers um diese in Bezeichnern zuzulassen.

Beachten Sie, dass macOS Sierra (10.12.5), um nur eine Plattform zu nennen, dies nicht unterstützt <uchar.h>.


36
2018-02-09 07:00



Dies FAQ ist eine Fülle von Informationen. Zwischen dieser Seite und Dieser Artikel von Joel SpolskyDu wirst einen guten Anfang haben.

Eine Schlussfolgerung kam ich auf dem Weg:

  • wchar_t ist 16 Bit unter Windows, aber nicht unbedingt 16 Bit auf anderen Plattformen. Ich denke, es ist ein notwendiges Übel auf Windows, aber wahrscheinlich kann anderswo vermieden werden. Der Grund, warum es unter Windows wichtig ist, ist, dass Sie Dateien verwenden müssen, die im Namen nicht-ASCII-Zeichen enthalten (zusammen mit der W-Version von Funktionen).

  • Beachten Sie, dass Windows-APIs, die ausgeführt werden wchar_t Zeichenfolgen erwarten UTF-16-Codierung. Beachten Sie auch, dass sich dies von UCS-2 unterscheidet. Beachten Sie die Ersatzpaare. Dies Testseite hat aufschlussreiche Tests.

  • Wenn Sie unter Windows programmieren, können Sie nicht verwenden fopen(), fread(), fwrite()usw., da sie nur nehmen char * und verstehe UTF-8-Codierung nicht. Macht die Portabilität schmerzhaft.


9
2018-02-09 16:34



Um strenge Unicode-Programmierung zu tun:

  • Verwenden Sie nur String-APIs, die Unicode-fähig sind (NICHT  strlen, strcpy, ... aber ihre breitspurigen Gegenstücke wstrlen, wsstrcpy, ...)
  • Wenn Sie mit einem Textblock arbeiten, verwenden Sie eine Codierung, die das Speichern von Unicode-Zeichen (utf-7, utf-8, utf-16, ucs-2, ...) ohne Verlust ermöglicht.
  • Überprüfen Sie, ob der Standardzeichensatz Ihres Betriebssystems Unicode-kompatibel ist (Beispiel: utf-8)
  • Verwenden Sie Schriftarten, die Unicode-kompatibel sind (z. B. arial_unicode)

Multi-Byte-Zeichenfolgen sind eine Codierung, die vor der UTF-16-Codierung liegt (die normalerweise mit wchar_t) und es scheint mir, es ist eher Windows-only.

Ich habe noch nie davon gehört wint_t.


7
2018-02-08 21:56



Das Wichtigste ist zu mache immer eine klare Unterscheidung zwischen Text- und Binärdaten. Versuche dem Modell von zu folgen Python 3.x str gegen bytes oder SQL TEXT gegen BLOB.

Leider verwirrt C das Problem durch Verwendung von char für beide "ASCII-Zeichen" und int_least8_t. Du wirst etwas tun wollen wie:

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

Vielleicht möchten Sie auch typedefs für UTF-16 und UTF-32-Code-Einheiten, aber das ist komplizierter, weil die Codierung von wchar_t ist nicht definiert. Sie müssen nur einen Präprozessor haben #ifs. Einige nützliche Makros in C und C ++ 0x sind:

  • __STDC_UTF_16__ - Wenn definiert, der Typ _Char16_t existiert und ist UTF-16.
  • __STDC_UTF_32__ - Wenn definiert, der Typ _Char32_t existiert und ist UTF-32.
  • __STDC_ISO_10646__ - Wenn definiert, dann wchar_t ist UTF-32.
  • _WIN32 - Unter Windows wchar_t ist UTF-16, obwohl dies den Standard bricht.
  • WCHAR_MAX - Kann verwendet werden, um die Größe von zu bestimmen wchar_t, aber nicht, ob das Betriebssystem es zur Darstellung von Unicode verwendet.

Bedeutet dies, dass mein Code sollte   Verwenden Sie keine Char-Typen überall und das   Funktionen müssen verwendet werden, die können   mit wint_t und wchar_t umgehen?

Siehe auch:

Nein. UTF-8 ist eine vollkommen gültige Unicode-Codierung, die verwendet char* Saiten. Es hat den Vorteil, dass, wenn Ihr Programm für Nicht-ASCII-Bytes transparent ist (z. B. ein Zeilenendekonverter, der auf handelt \r und \n aber durch andere Zeichen unverändert), müssen Sie überhaupt keine Änderungen vornehmen!

Wenn Sie mit UTF-8 arbeiten, müssen Sie alle Annahmen ändern, die char= Zeichen (z. B. nicht anrufen toupper in einer Schleife) oder char = Bildschirmspalte (z. B. für Textumbruch).

Wenn Sie mit UTF-32 arbeiten, haben Sie die Einfachheit von Zeichen fester Breite (aber nicht fester Breite) Grapheme, muss aber den Typ aller Strings ändern).

Wenn Sie mit UTF-16 arbeiten, müssen Sie die Annahme von Zeichen mit fester Breite verwerfen und die Annahme von 8-Bit-Code-Einheiten, was dies zu dem schwierigsten Upgrade-Pfad von Einzelbyte-Codierungen macht.

Ich würde es aktiv empfehlen vermeiden  wchar_t weil es nicht plattformübergreifend ist: Manchmal ist es UTF-32, manchmal ist es UTF-16, und manchmal ist es eine vor-Unicode-ostasiatische Kodierung. Ich würde empfehlen, zu verwenden typedefs 

Noch wichtiger, vermeiden TCHAR.


3
2017-08-18 13:45



Sie wollen im Grunde mit Strings im Speicher als wchar_t Arrays statt char arbeiten. Wenn Sie irgendeine Art von I / O (wie das Lesen / Schreiben von Dateien) machen, können Sie mit UTF-8 (das ist wahrscheinlich die gebräuchlichste Kodierung) kodieren / decodieren, was einfach genug ist, um es zu implementieren. Googeln Sie einfach die RFCs. Also sollte In-Memory nichts Multi-Byte sein. Ein wchar_t repräsentiert ein Zeichen. Wenn Sie jedoch zum Serialisieren kommen, müssen Sie in etwas wie UTF-8 codieren, wo einige Zeichen durch mehrere Bytes dargestellt werden.

Sie müssen auch neue Versionen von strcmp usw. für die breiten Zeichenfolgen schreiben, aber das ist kein großes Problem. Das größte Problem wird Interop mit Bibliotheken / existierendem Code sein, der nur Char-Arrays akzeptiert.

Und wenn es um sizeof (wchar_t) geht (Sie benötigen 4 Bytes, wenn Sie es richtig machen wollen), können Sie es jederzeit mit typedef / macro hacks neu definieren, wenn Sie müssen.


2
2018-02-09 06:40



Ich würde keiner Standardbibliotheksimplementierung vertrauen. Rollen Sie einfach Ihre eigenen Unicode-Typen.

#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



Soweit ich weiß, ist wchar_t implementierungsabhängig (wie man daraus ersehen kann) Wiki-Artikel). Und es ist nicht Unicode.


1
2018-02-09 06:03