99 Stimmen

C-Programmierung: Wie programmiert man für Unicode?

Welche Voraussetzungen müssen erfüllt sein, um strikte Unicode-Programmierung zu betreiben?

Bedeutet dies, dass mein Code nicht char und dass Funktionen verwendet werden müssen, die sich mit wint_t y wchar_t ?

Und welche Rolle spielen Multibyte-Zeichenfolgen in diesem Szenario?

56voto

Jonathan Leffler Punkte 694013

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 enthalten können, ist ihr Wert etwas begrenzt. Für eine bestimmte Implementierung bieten sie nützliche Unterstützung, aber wenn Ihr Code in der Lage sein muss, zwischen verschiedenen Implementierungen zu wechseln, gibt es keine ausreichende Garantie, dass sie nützlich sind.

Folglich ist der von Hans van Eck vorgeschlagene Ansatz (einen Wrapper um die ICU - International Components for Unicode - Bibliothek herum zu schreiben), IMO, vernünftig.

Die UTF-8-Kodierung hat viele Vorteile. Einer davon ist, dass die Daten, wenn man sie nicht manipuliert (z. B. durch Abschneiden), von Funktionen kopiert werden können, die die Feinheiten der UTF-8-Kodierung nicht kennen. Dies ist kategorisch nicht der Fall bei wchar_t .

Unicode in voller Länge ist ein 21-Bit-Format. Das heißt, Unicode reserviert Codepunkte von U+0000 bis U+10FFFF.

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

UTF-8 ist bekanntlich ein Multi-Byte-Format mit einer sorgfältigen Struktur, die es ermöglicht, den Anfang von Zeichen in einer Zeichenkette zuverlässig zu finden, und zwar an jeder Stelle der Zeichenkette. Bei Einzelbyte-Zeichen ist das High-Bit auf Null gesetzt. Bei Multi-Byte-Zeichen beginnt das erste Zeichen mit einem der Bitmuster 110, 1110 oder 11110 (für 2-Byte-, 3-Byte- oder 4-Byte-Zeichen), wobei die nachfolgenden Bytes immer mit 10 beginnen. Die Fortsetzungszeichen liegen immer im Bereich 0x80 0xBF. Es gibt Regeln, dass UTF-8-Zeichen im kleinstmöglichen Format dargestellt werden müssen. Eine Folge dieser Regeln ist, dass die Bytes 0xC0 und 0xC1 (auch 0xF5..0xFF) in gültigen UTF-8-Daten nicht vorkommen 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-Codesatz sein würde und alles in einen 16-Bit-Codebereich passen würde. Leider ist die reale Welt komplexer, und der Code musste auf die aktuelle 21-Bit-Kodierung erweitert werden.

UTF-16 ist also ein Einheitscode (16-Bit-Wort) für die "Basic Multilingual Plane", d. h. die Zeichen mit den 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-Kodierung arbeitet, in der Lage sein, Kodierungen mit variabler Breite zu handhaben, genau wie UTF-8. Die Codes für die Zeichen mit zwei Einheiten werden Surrogate genannt.

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

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

Viele weitere Informationen finden Sie auf der INTENSIVSTATION und Unicode-Websites.

C11 und <uchar.h>

Mit der C11-Norm wurden die Regeln geändert, aber selbst jetzt (Mitte 2017) haben noch nicht alle Implementierungen mit den Änderungen Schritt gehalten. Der C11-Standard fasst die Änderungen für die Unicode-Unterstützung wie folgt zusammen:

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

Im Folgenden wird nur ein minimaler Überblick über die Funktionalität gegeben. Die Spezifikation umfasst:

6.4.3 Universelle Zeichennamen

Syntax
Universal-Zeichen-Name:
    \u hex-quad
    \U hex-quad hex-quad
hex-quad:
     hexadezimal-stellig hexadezimal-stellig hexadezimal-stellig hexadezimal-stellig

7.28 Unicode-Hilfsmittel <uchar.h>

Die Überschrift <uchar.h> deklariert Typen und Funktionen für die Bearbeitung von Unicode-Zeichen.

Die deklarierten Typen sind mbstate_t (beschrieben in 7.29.1) und size_t (beschrieben in 7.19);

char16_t

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

char32_t

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

(Ich übersetze die Querverweise: <stddef.h> definiert size_t , <wchar.h> definiert mbstate_t , und <stdint.h> definiert uint_least16_t y uint_least32_t .) Die <uchar.h> Kopfzeile definiert auch einen minimalen Satz von (neu startbaren) Konvertierungsfunktionen:

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

Es gibt Regeln dafür, welche Unicode-Zeichen in Bezeichnern verwendet werden können, die die \unnnn ou \U00nnnnnn Notizen. Möglicherweise müssen Sie die Unterstützung für solche Zeichen in Bezeichnern aktiv aktivieren. Der GCC verlangt zum Beispiel -fextended-identifiers um diese in Bezeichnern zuzulassen.

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

5 Stimmen

Ich glaube, Sie verkaufen wchar_t und Freunde ein wenig zu kurz kommen. Diese Typen sind wichtig, damit die C-Bibliothek Text in jede Kodierung (einschließlich Nicht-Unicode-Kodierungen). Ohne die breiten Zeichentypen und -funktionen würde die C-Bibliothek eine Reihe von Textverarbeitungsfunktionen benötigen für jede unterstützte Kodierung: Stellen Sie sich vor, Sie hätten koi8len, koi8tok, koi8printf nur für KOI-8 kodierten Text, und utf8len, utf8tok, utf8printf für UTF-8 Text. Stattdessen haben wir das Glück, dass wir nur eine Menge dieser Funktionen (die ursprünglichen ASCII-Funktionen nicht mitgerechnet): wcslen , wcstok y wprintf .

2 Stimmen

Ein Programmierer muss lediglich die Zeichenumwandlungsfunktionen der C-Bibliothek verwenden ( mbstowcs und Freunde), um jede unterstützte Kodierung in wchar_t . Einmal in wchar_t Format kann der Programmierer den einzigen Satz von Funktionen zur Handhabung von breitem Text verwenden, den die C-Bibliothek bietet. Eine gute C-Bibliotheksimplementierung unterstützt praktisch jede Kodierung, die die meisten Programmierer jemals benötigen werden (auf einem meiner Systeme habe ich Zugriff auf 221 einzigartige Kodierungen).

1 Stimmen

Was die Frage betrifft, ob sie breit genug sein werden, um nützlich zu sein: Der Standard verlangt, dass eine Implementierung garantieren muss, dass wchar_t ist breit genug, um jedes von der Implementierung unterstützte Zeichen zu enthalten. Das bedeutet, dass (mit vielleicht einer bemerkenswerten Ausnahme) die meisten Implementierungen sicherstellen, dass sie breit genug sind, dass ein Programm, das wchar_t wird jede vom System unterstützte Kodierung verarbeiten (Microsofts wchar_t ist nur 16 Bit breit, was bedeutet, dass ihre Implementierung nicht alle Kodierungen vollständig unterstützt, vor allem nicht die verschiedenen UTF-Kodierungen, aber das ist die Ausnahme, nicht die Regel).

22voto

Hans van Eck Punkte 380

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

In meiner Firma haben wir eine Wrapper-Bibliothek um die ICU-Bibliothek von IBM herum entwickelt. 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 Gedanken über Leistungseinbußen gemacht. Wenn die Leistung ein Problem war, haben wir auch UTF-16-Schnittstellen bereitgestellt (unter Verwendung unseres eigenen Datentyps).

Die Anwendungen könnten weitgehend so bleiben, wie sie sind (unter Verwendung von char), obwohl sie in einigen Fällen bestimmte Aspekte berücksichtigen müssen. Zum Beispiel verwenden wir anstelle von strncpy() einen Wrapper, der das Abschneiden von UTF-8-Sequenzen vermeidet. In unserem Fall ist dies ausreichend, aber man könnte auch Prüfungen für die Kombination von Zeichen in Betracht ziehen. Wir haben auch Wrapper für die Zählung der Anzahl der Codepoints, der Anzahl der Grapheme, usw.

Wenn wir mit anderen Systemen zusammenarbeiten, müssen wir manchmal eine benutzerdefinierte Zeichenkomposition vornehmen, so dass Sie hier eine gewisse Flexibilität benötigen (abhängig von Ihrer Anwendung).

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

3 Stimmen

Eine gültige UTF-8-Bytefolge würde von strncpy niemals abgeschnitten (truncated) werden. Gültige UTF-8-Sequenzen dürfen keine 0x00-Bytes enthalten (außer dem abschließenden Null-Byte natürlich).

10 Stimmen

@Dan Moulding: Wenn Sie strncpy(), sagen wir, eine Zeichenkette, die ein einzelnes chinesisches Zeichen enthält (das 3 Byte lang sein kann), in ein 2-Byte-Zeichen-Array übertragen, erzeugen Sie eine ungültige UTF-8-Sequenz.

2 Stimmen

@Hans van Eck: Wenn Ihr Wrapper dieses einzelne chinesische 3-Byte-Zeichen in ein 2-Byte-Array kopiert, dann werden Sie es entweder abschneiden und eine ungültige Sequenz erzeugen, oder Sie werden ein undefiniertes Verhalten haben. Wenn man Daten kopiert, muss das Ziel natürlich groß genug sein, das versteht sich von selbst. Mein Punkt war, dass strncpy richtig verwendet wird, ist die Verwendung mit UTF-8 völlig sicher.

12voto

dbyron Punkte 481

Diese FAQ ist eine Fülle von Informationen. Zwischen dieser Seite und dieser Artikel von Joel Spolsky dann haben Sie einen guten Start.

Eine Schlussfolgerung, zu der ich auf dem Weg gekommen bin:

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

  • Beachten Sie, dass Windows-APIs, die die wchar_t Strings erwarten UTF-16-Kodierung. Beachten Sie auch, dass dies etwas anderes ist als UCS-2. Beachten Sie die Surrogatpaare. Diese Testseite hat aufschlussreiche Tests.

  • Wenn Sie unter Windows programmieren, können Sie nicht mit fopen() , fread() , fwrite() usw., da sie nur die char * und die UTF-8-Kodierung nicht verstehen. Das macht die Portabilität schwierig.

0 Stimmen

Beachten Sie, dass stdio f* und Freunde arbeiten mit char * において jede Plattform, weil es der Standard so vorsieht - verwenden wcs* stattdessen für wchar_t.

0 Stimmen

Beachten Sie, dass Spolskys Artikel größtenteils gültig bleibt. Er behauptet jedoch, dass UTF-8 bis zu 6 Bytes für ein einzelnes Zeichen verwenden könnte. In der Praxis begrenzt Unicode den Bereich der Codepunkte auf U+0000 U+10FFFF. Und alle diese Zeichen können in UTF-8 in 1-4 Byte kodiert werden. Als Folge der Kodierungsregeln können die Bytes 0xC0, 0xC1, 0xF5-0xFF nicht in gültigem UTF-8 erscheinen.

8voto

sebastien Punkte 327

Für eine strikte Unicode-Programmierung:

  • Verwenden Sie nur String-APIs, die Unicode-fähig sind ( NICHT strlen , strcpy ... aber ihre weit verbreiteten Gegenstücke wstrlen , wsstrcpy , ...)
  • Wenn Sie mit einem Textblock arbeiten, verwenden Sie eine Kodierung, die es erlaubt, Unicode-Zeichen (utf-7, utf-8, utf-16, ucs-2, ...) ohne Verlust zu speichern.
  • Prüfen Sie, ob der Standardzeichensatz Ihres Betriebssystems mit Unicode kompatibel ist (z. B. utf-8).
  • Verwenden Sie Schriftarten, die mit Unicode kompatibel sind (z. B. arial_unicode)

Multi-Byte-Zeichenfolgen ist eine Kodierung, die vor der UTF-16-Kodierung (die normalerweise mit wchar_t ) und ich habe den Eindruck, dass sie nur für Windows gilt.

Ich habe noch nie etwas von wint_t .

0 Stimmen

Wint_t ist ein in <wchar.h> definierter Typ, genau wie wchar_t. Er hat die gleiche Rolle in Bezug auf breite Zeichen, die int in Bezug auf 'char' hat; er kann jeden breiten Zeichenwert oder WEOF enthalten.

5voto

dan04 Punkte 82011

Das Wichtigste ist stets eine klare Unterscheidung zwischen Text und Binärdaten treffen . Versuchen Sie, dem Modell der Python 3.x str vs. bytes oder SQL TEXT vs. BLOB .

Leider verwirrt C das Problem durch die Verwendung von char sowohl für "ASCII-Zeichen" als auch für int_least8_t . Sie werden etwas tun wollen wie:

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

Sie könnten auch Typendefinitionen für UTF-16- und UTF-32-Codeeinheiten benötigen, aber das ist komplizierter, weil die Kodierung von wchar_t ist nicht definiert. Sie müssen einfach einen Präprozessor #if s. Einige nützliche Makros in C und C++0x sind:

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

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

Siehe auch:

Nein. UTF-8 ist eine vollkommen gültige Unicode-Kodierung, die char* Strings. Dies hat den Vorteil, dass Ihr Programm für Nicht-ASCII-Bytes transparent ist (z. B. ein Zeilenendkonverter, der auf \r y \n aber andere Zeichen unverändert durchläuft), müssen Sie überhaupt keine Änderungen vornehmen!

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

Wenn Sie sich für UTF-32 entscheiden, haben Sie die Einfachheit von Zeichen mit fester Breite (aber nicht mit fester Breite Grapheme aber Sie müssen den Typ aller Ihrer Zeichenketten ändern).

Wenn Sie sich für UTF-16 entscheiden, müssen Sie sowohl die Annahme von Zeichen mit fester Breite als auch die Annahme von Zeichen mit fester Breite verwerfen und die Annahme von 8-Bit-Code-Einheiten, was diesen Weg zum schwierigsten Upgrade von Einzelbyte-Kodierungen macht.

Ich würde empfehlen, aktiv zu vermeiden. wchar_t weil es nicht plattformübergreifend ist: Manchmal handelt es sich um UTF-32, manchmal um UTF-16 und manchmal um eine ostasiatische Vor-Unicode-Kodierung. Ich empfehle die Verwendung von typedefs

Und was noch wichtiger ist, vermeiden TCHAR .

0 Stimmen

Ich denke nicht, dass das unglücklich ist - der Char ist ein int. Das ist ein Vorteil. Die Verwendung von wörtlichen Zeichenkonstanten fällt mir als eine Möglichkeit ein. Und Funktionen, die eine char * können Probleme haben, wenn sie eine const char * wenn ich mich recht erinnere (aber ich bin mir nicht ganz sicher, welche Funktionen das sind, also bitte mit Vorsicht genießen). Nur weil es bei anderen Sprachen komplizierter ist, bedeutet das nicht, dass es ein schlechtes Design ist.

2 Stimmen

Da einfache char kann signiert werden, indem man einfache Zeichen für UTF8 riskiert Probleme mit der Zeichenerweiterung. Verwenden Sie unsigned char auch für UTF8 - oder uint8_t .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X