Zum Hauptinhalt springen
  1. Artikel/

Buchstabensalat

Autor
Christian Schärf

Vor einiger Zeit tauchte dies in der Datenbank einer Webseite auf, die ich betreue:

Jubiläumsschrift.pdf
Jubiläumsschrift.pdf
Jubiläumsschrift.pdf
Jubiläumsschrift.pdf

Offenbar sollte dort Jubiläumsschrift.pdf stehen, aber der Umlaut ist auf sehr abenteuerlich anmutende Weise kaputt gegangen. Wieso?

Zeichenkodierungen
#

Text muss für die Verarbeitung in Computern in eine Abfolge von Bytes umgewandelt werden. Ein Byte ist 8 Binärstellen (Bit) lang und kann damit Werte von Null (einschließlich) bis 256 (ausschließlich) annehmen. Das Verfahren dafür heißt Zeichenkodierung (engl. character encoding). Anfangs wurde eine einfache Zuordnung von einzelnen Zeichen zu Zahlen vorgenommen.

ASCII
#

Die erste Zeichenkodierung mit großer Verbreitung war ASCII (American Standard Code for Information Interchange, Amerikanischer Standardcode zum Informationsaustausch). Dieser hatte nur 7 Bit, da sich das aus 8 Bit bestehende Byte zu dieser Zeit noch nicht durchgesetzt hatte. ASCII codiert somit 128 Zeichen, darunter sind

  • die jeweils 26 Groß- und Kleinbuchstaben des lateinischen Alphabets,
  • 10 Ziffern,
  • einige Satzzeichen,
  • einige Sonderzeichen
  • Steuerzeichen. Diese kodieren beispielsweise das Ende einer Datei und den Zeilenumbruch.

8-Bit-Wildwuchs
#

Das funktioniert super, solange man nur englische Texte und Programmcodes verarbeiten will (und anderen Währungssymbole als das Dollarzeichen $ benötigt sowie keinen Wert auf „typografisch korrekte“ Anführungszeichen legt). Da Computer irgendwann aber auch in anderen Ländern verwendet wurden, musste der Zeichenvorrat erweitert werden. Mittlerweile hatte sich das 8-Bit-Byte durchgesetzt. Da jedes Zeichen sowieso in einem ganzen Byte gespeichert wurde, war in ASCII noch ein Bit übrig und konnte frei verwendet werden! Und damit begann der Wildwuchs an Zeichenkodierungen.

Es konnten also einige weitere Schriftsysteme kodiert werden. Weit verbreitet ist die ISO 8859-Familie, die 15 verschiedene Kodierungen für auf dem Lateinischen basierende Alphabete sowie Hebräisch und Arabisch umfasst.1 Da Microsoft traditionell wenig Wert auf Standards und Interoperabilität legt, enthält Windows eigene Kodierungen, die Windows Code Pages.

Alle diese haben die Gemeinsamkeit, dass die ersten 128 Zeichen die gleichen sind wie in ASCII. In ASCII kodierte Texte werden damit auch in den 8-Bit-Kodierungen korrekt dargestellt.

Auch dies funktioniert super, solange bekannt ist, welche Kodierung für einen gegebenen Text verwendet wurde. Für eigene Dateien ist das einfach, aber irgendwann kam auch dieses Internet auf und der Austausch von Daten (insbesondere in Form von Webseiten und E-Mails) zwischen verschiedenen Computern nahm zu. Damit musste mit jedem übertragenen Text auch die Kodierung immer angegeben werden (und die beteiligte Software musste die Kodierungen korrekt verarbeiten können).

Aber es gab noch weitere Probleme:

  • Texte mit verschiedenen Sprachen, deren Zeichen nicht in einem einzigen Zeichensatz enthalten waren, konnten nicht verarbeitet werden.
  • Schriftsysteme mit mehr als 128 Zeichen zusätzlich zum lateinischen Alphabet konnten nicht unterstützt werden (stattdessen mussten gänzlich andere Kodierungen verwendet werden).
  • Da alle Plätze der 8-Bit-Kodierungen von Anfang an belegt waren, konnten neue Zeichen nur durch neue Kodierungen unterstützt werden. So wurde ISO 8859-1 (Westeuropäisch) durch ISO 8859-15 (Westeuropäisch) abgelöst, der auch das Euro-Zeichen enthält. Das noch später eingeführte große Eszett ist aber auch hier nicht enthalten.

Unicode
#

Um all dies zu lösen, wurde Unicode entwickelt. Damit sollten endlich alle Zeichen, die irgendwo auf der Welt Verwendung finden, einheitlich kodiert werden. Anfangs waren 65536 Zeichen vorgesehen, die in 16 Bit passen, da dies ausreicht, um die Zeichen aller lebendigen Sprachen zu kodieren (Haha, nein).

Allerdings ist Unicode keine wirkliche Zeichenkodierung, sondern eigentlich nur eine Liste von sogenannten Codepunkten (engl. code points). Codepunkte können Zeichen bzw. deren Bestandteile sein oder andere Zwecke erfüllen, beispielsweise Zeilenumbrüche oder Ligaturen beeinflussen. Soll ein Codepunkt referenziert werden, geschieht dies üblicherweise in der Form U+<Nummer>, wobei <Nummer> als Hexadezimalzahl angegeben und großzügigerweise auf 4 Stellen aufgefüllt wird. Jedenfalls ist ein Text eine Abfolge von Codepunkten. Um diese Abfolge in Codepunkten als Abfolge von Bytes darzustellen (denn nur die können von Computern verarbeitet werden), braucht es noch eine Unicode-Kodierung.

Eine wichtige Eigenschaft von Unicode ist die Vorwärtskompatibilität: Kein Unicode-Codepunkt wird in einer zukünftigen Version des Standards gelöscht oder an etwas anderes vergeben. Und das wurde bisher auch so eingehalten, selbst bei Zeichen, deren Bedeutung unbekannt ist und die möglicherweise versehentlich in Unicode aufgenommen wurden.2

Die ersten 128 Codepunkte sind dabei die ASCII-Zeichen. Zwar mutet es vielleicht seltsam an, dass das Dollarzeichen $ zum lateinischen Grundalphabet gehört, allerdings wird damit eine gewisse Kompatibilität mit älteren Kodierungen ermöglicht (s. unten). In ähnlicher Weise sind die Codepunkte 128—256 die gleichen, die in ISO 8859-1 in diesem Bereich kodiert sind.

Aber auch die Codepunkte von Unicode sind nicht frei von Problemen: Viele Grapheme (unter Nicht-Linguisten auch als „Zeichen“ bekannt) lassen sich auf verschiedene Weise in Codepunkten darstellen. So ist beispielsweise das große Ä (A-Umlaut) als Codepunkt U+00C4 kodiert, kann aber genauso als U+0041 (lateinischer Großbuchstabe A) gefolgt von U+0308 (Kombinierende Diäresis) dargestellt werden: . (Dazu sei angemerkt, dass U+0308 Kombinierende Diäresis von U+00A8 Diäresis ¨ unterschieden werden muss.) Ähnliche Redundanzen gibt es in der Hangul-Schrift, die für das Koreanische verwendet wird: Hier sind sowohl die Jamo (Buchstaben) in Unicode kodiert, als auch die aus den Jamo zusammengesetzten Silbenzeichen. Außerdem sind einige Ligaturen wie (U+FB01 lateinische kleingeschriebene Ligatur FI) und (U+FB04 lateinische kleingeschriebene Ligatur FFL) in Unicode codiert, die eigentlich nur die aufeinanderfolgenden Zeichen fl bzw. ffl darstellen.

Auch einige Schriftzeichen, die in mehreren ostasiatischen Schriften verwendet werden, dort aber unterschiedliche Bedeutungen haben, sind unter dem selben Codepunkt kodiert. Diese kontroverse Praxis wird als Han-Vereinheitlichung bezeichnet. Die Motivation dahinter ist teilweise technischer Natur: Würden alle Zeichen in allen Varianten separat kodiert, dann würde der ursprünglich geplante Vorrat von 65536 Codepunkten nicht ausreichen. Außerdem wurde die Einschätzung vertreten, dass die in den verschiedenen Sprachen gebräuchlichen Varianten lediglich unterschiedliche Darstellungsformen seien und keine (ausreichende) verschiedene Bedeutung haben, die eine separate Kodierung rechtfertigen könnte. Es muss dennoch festgehalten werden, dass den europäischen Schriften dieses Schicksal nicht wiederfahren ist: Das lateinische A, das kyrillische А, und das griechische Alpha (Α) sind alle unterschiedlich kodiert. Im Griechischen sind sogar typografische Varianten von einigen Buchstaben kodiert, beispielsweise θ und ϑ, ς und σ, sowie φ und ϕ.

Mit der Version 2.0 des Unicode-Standards wurde auch die Begrenzung auf 65536 Codepunkte aufgehoben. Damit ist genug Platz auch für weniger gebräuchliche ostasiatische Schriftzeichen. Auch zahlreiche historische Schriftsysteme, die heute nicht mehr verwendet werden, wie ägyptische Hieroglyphen oder Keilschrift, wurden in den Unicode-Standard aufgenommen.

Unicode-Kodierungen
#

Die Unicode-Codepunkte sind nur ein Teil der Geschichte. Um einen Text, dargestellt als Abfolge von Codepunkten, im Computer zu speichern und zu übertragen, muss er in eine Abfolge von Bytes umgewandelt werden. Diese Umwandlung wird durch eine Unicode-Kodierung festgelegt. Dabei wird zwischen Kodierungen mit fester Breite und solchen mit variabler Breite unterschieden.

Heute ist UTF-8 die am weitesten verbreitete Kodierung überhaupt und hat eine variable Breite. Ein Codepunkt nimmt hier 1 bis 4 Byte (8 bis 32 Bit) ein. Weil die ersten 128 Codepunkte (also die ASCII-Zeichen) in ein Byte kodiert werden, kann ein in ASCII kodierter Text auch als UTF-8-kodierter Text verarbeitet werden. Außerdem benötigen Texte, die hauptsächlich aus diesen Zeichen bestehen (also solche, die das lateinische Alphabet verwenden), weniger Speicherplatz als bei anderen Unicode-Kodierungen und nur wenig mehr als bei älteren 8-Bit-Kodierungen.

Bei Kodierungen mit variabler Breite kann die Anzahl der Codepunkte nicht mehr so einfach aus der Menge des belegten Speicherplatzes abgelesen werden. Stattdessen muss der gesamte Text durchsucht die Codepunkte dabei gezählt werden. Das ist in der Praxis glücklicherweise irrelevant, unter anderem, da die Anzahl der Codepunkte sowieso keine Bedeutung hat.

Als Unicode noch für höchstens 65536 Codepunkte ausgelegt war, gab es die Kodierung UCS-2, die eine feste Breite von 16 Bit (2 Byte) hat und daher jeden Unicode-Codepunkt in 16 Bit kodiert. Als sich herausstellte, dass dieser Zeichenvorrat nicht ausreicht, wurde UCS-2 zu UTF-16 weiterentwickelt. Dies ist eine Kodierung, die häufig zur software-internen Darstellung von Texten verwendet wird und ebenfalls eine variable Länge hat. Im Unterschied zu UTF-8 ist eine Codeeinheit (engl. code unit) hier 16 Bit breit. Ein Unicode-Codepunkt wird daher in 16 oder 32 Bit (2 oder 4 Byte) kodiert. Die heute noch funktionierende Kodierung mit fester Breite ist UTF-32 (bzw. UCS-4), die jeden Codepunkt mit 32 Bit (4 Byte) kodiert.

Der Buchstabensalat
#

Wie kam nun der Buchstabensalat vom Anfang des Artikels zustande? Es wurde ein in UTF-8 kodierter Text so interpretiert, als wäre er in der Windows Code Page CP1252 kodiert und das Ergebnis dann wieder in UTF-8 kodiert. Dies wurde mehrfach wiederholt.

Im Wort „Jubiläumsschrift“ ist der Buchstabe ä nicht Teil des lateinischen Grundalphabets und deshalb nicht Teil von ASCII. In UTF-8 wird dieser Buchstabe deshalb als einziger des Wortes in zwei Codeeinheiten (die jeweils ein Byte lang sind) kodiert. Insgesamt ist „Jubiläumsschrift“ in UTF-8 kodiert also:

J  u  b  i  l  ä     u  m  s  s  c  h  r  i  f  t
4A 75 62 69 6C C3 A4 75 6D 73 73 63 68 72 69 66 74

Diese Byte-Abfolge wurde dann fälschlicherweise als in CP1252 kodierter Text aufgefasst. Für alle ASCII-Zeichen (hier die Buchstaben des lateinischen Grundalphabets) sind das die gleichen ursprünglich kodierten Zeichen, da diese in UTF-8 und CP1252 mit den gleichen Bytes kodiert werden. Lediglich für ä gibt es in diesem Beispiel einen Unterschied: In UTF-8 wird es mit den zwei Bytes C3 A4 kodiert. In CP1252 hingegen ist jedes Zeichen nur ein Byte lang. ä wird mit E4 kodiert. Die Byte-Abfolge C3 A4 entspricht hingegen den zwei Zeichen ä. Damit wird die obige Byte-Abfolge als dieser Text aufgefasst:

4A 75 62 69 6C C3 A4 75 6D 73 73 63 68 72 69 66 74
J  u  b  i  l  Ã  ¤  u  m  s  s  c  h  r  i  f  t

Nun beginnt das Spiel erneut: Der Text wird wieder in UTF-8 kodiert. Dabei sind die Zeichen à und ¤ die einzigen nicht-ASCII-Zeichen und werden in jeweils 2 Bytes kodiert:

J  u  b  i  l  Ã     ¤     u  m  s  s  c  h  r  i  f  t
4A 75 62 69 6C C3 83 C2 A4 75 6D 73 73 63 68 72 69 66 74

Als CP1252 interpretiert, ergibt sich dieser Text:

4A 75 62 69 6C C3 83 C2 A4 75 6D 73 73 63 68 72 69 66 74
J  u  b  i  l  Ã  ƒ  Â  ¤  u  m  s  s  c  h  r  i  f  t

Wird dies noch mehrmals wiederholt, resultiert irgendwann das Ungetüm vom Anfang.

Ausprobieren
#

Hier kann selbst untersucht werden, welche Texte nach einer oder mehreren falschen Kodierungen resultieren. Tipp: Als Anfangswert etwas wählen, das nicht-ASCII-Zeichen enthält.

Weiterlesen
#

https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/