Unicode in C-Zeichen gespeichert

Lesezeit: 10 Minuten

Benutzer-Avatar
Miroslav Stuten

Ich lerne jetzt die C-Sprache unter Linux und bin auf eine etwas seltsame Situation gestoßen.

Soweit ich weiß, die Standard-C’s char Datentyp ist ASCII, 1 Byte (8 Bit). Es sollte bedeuten, dass es nur ASCII-Zeichen aufnehmen kann.

In meinem Programm verwende ich char input[]die gefüllt ist mit getchar funktionieren wie dieser Pseudocode:

char input[20];
int z, i;
for(i = 0; i < 20; i++)
{
   z = getchar();
   input[i] = z;
}

Das Seltsame ist, dass es nicht nur für ASCII-Zeichen funktioniert, sondern für jedes Zeichen, das ich mir vorstelle, wie z @&@{čřžŧ¶'`[łĐŧđж←^€~[←^ø{&}čž on the input.

My question is – how is it possible? It seems to be one of many beautiful exceptions in C, but I would really appreciate explanation. Is it a matter of OS, compiler, hidden language’s additional super-feature?

Thanks.

  • It’s not really characters, it’s bytes that are gotten with getchar(). Every character is encoded as a byte sequence.

    – Daniel Fischer

    Apr 4, 2012 at 18:45

  • These are relatively normal characters. Try widening your imagination to include, say, some Chinese or Japanese letters. Or try Cyrillic for a change 🙂 Here’s “Hello” in Russian for you: “Привет”.

    – Sergey Kalinichenko

    Apr 4, 2012 at 18:45


  • @DanielFischer I understand, that getchar() decodes it into byte(s). But I already don’t understand, how that bytes can be held in char data type, which should be one byte.

    – Miroslav Mares

    Apr 4, 2012 at 18:48

  • No, getchar() doesn’t decode it into bytes. The input buffer from which getchar() reads already contains the possibly several bytes making up the character you typed. Each getchar() gets you one of the bytes, so for UTF-8 encoded input, a character can take up to four getchar(). When you print it out, the byte sequence is sent to the terminal and that translates it into glyphs.

    – Daniel Fischer

    Apr 4, 2012 at 18:53

  • Great, thanks, I completely understand now!

    – Miroslav Mares

    Apr 4, 2012 at 19:03

user avatar
jsbueno

There is no magic here – The C language gives you acess to the raw bytes, as they are stored in the computer memory.
If your terminal is using utf-8 (which is likely), non-ASCII chars take more than one byte in memory. When you display then again, is our terminal code which converts these sequences into a single displayed character.

Just change your code to print the strlen of the strings, and you will see what I mean.

To properly handle utf-8 non-ASCII chars in C you have to use some library to handle them for you, like glib, qt, or many others.

  • or try to print just input[ 0 ] um zu sehen, dass es nicht das erste Zeichen druckt, sondern nur das erste Byte, das höchstwahrscheinlich ein nicht druckbares Zeichen sein wird, und versuchen Sie dann, die Eingabe zu drucken[ 0 ] und Eingabe[ 1 ] zusammen, um das Multibyte-Zeichen zu sehen.

    – abresas

    4. April 2012 um 18:48 Uhr

  • Ok, ich habe gerade einige Codeänderungen ausprobiert und es funktioniert genau wie beschrieben. Vielen Dank. Nur eine Anmerkung zu breiten Zeichen – <wchar.h> ist nicht genug für die richtige Handhabung von breiten Zeichen?

    – Miroslav Stuten

    4. April 2012 um 19:07 Uhr


Benutzer-Avatar
Morphfh

ASCII ist ein 7-Bit-Zeichensatz. In C normalerweise durch ein 8-Bit-Zeichen dargestellt. Wenn das höchste Bit in einem 8-Bit-Byte gesetzt ist, ist es das nicht ein ASCII-Zeichen.

Beachten Sie auch, dass Sie es sind nicht garantiert ASCII Als Basis ignorieren viele andere Szenarien. Wenn Sie überprüfen möchten, ob a “Primitive” byte ist ein alpha-zeichen man kann also nicht, bei beachtung aller systeme, sagen:

is_alpha = (c > 0x40 && c < 0x5b) || (c > 0x60 && c < 0x7b);

Stattdessen müssen Sie verwenden ctype.h und sag:

isalpha(c);

Die einzige Ausnahme, AFAIK, sind Zahlen, zumindest in den meisten Tabellen haben sie zusammenhängende Werte.

So funktioniert das;

char ninec="9";
char eightc="8";

int nine  = ninec  - '0';
int eight = eightc - '0';

printf("%d\n", nine);
printf("%d\n", eight);

Aber das ist nicht garantiert ‘a’:

alhpa_a = 0x61;

Systeme, die nicht auf ASCII basieren, dh mit EBCDIC; C auf einer solchen Plattform läuft immer noch gut, aber hier verwenden sie (meistens) 8 Bits anstelle von 7 und dh A kann dezimal kodiert werden 193 und nicht 65 wie es in ASCII ist.


Für ASCII jedoch; Bytes mit einer Dezimalzahl von 128 – 255 (8 Bits in Verwendung), wird erweitert und ist nicht Teil des ASCII-Satzes. Dh ISO-8859 verwendet diesen Bereich.

Was wird oft gemacht; ist auch, zwei oder mehr Bytes zu einem Zeichen zusammenzufassen. Wenn Sie also zwei Bytes nacheinander drucken, ist dies beispielsweise wie folgt definiert: utf8 0xc3 0x98 == Ø, dann bekommst du dieses Zeichen.

Dies hängt wiederum davon ab, in welcher Umgebung Sie sich befinden. Auf vielen Systemen/Umgebungen führt das Drucken von ASCII-Werten über Zeichensätze, Systeme usw. hinweg zum gleichen Ergebnis. Das Drucken von Bytes > 127 oder Double-Byte-Zeichen führt jedoch je nach lokaler Konfiguration zu einem anderen Ergebnis.

Dh:

Herr A läuft das Programm bekommt

Jasŋ€

Während Mr. B bekommt

Jaß

Dies ist möglicherweise besonders relevant für die ISO-8859-Serie und Windows-1252 der Einzelbyte-Darstellung von erweiterten Zeichen usw.


  • UTF-8#Codepage_layoutIn UTF-8 haben Sie ASCII, dann haben Sie spezielle Folgen von Byes.
    • Jede Sequenz beginnt mit einem Byte > 127 (das letzte ASCII-Byte),
    • gefolgt von einer bestimmten Anzahl von Bytes, die alle mit den Bits beginnen 10.
    • Mit anderen Worten, Sie werden niemals ein ASCII-Byte in einer Multi-Byte-UTF-8-Darstellung finden.

Das ist; Das erste Byte in UTF-8, wenn nicht ASCII, gibt an, wie viele Bytes dieses Zeichen hat. Man könnte auch sagen, dass ASCII-Zeichen sagen, dass keine weiteren Bytes folgen – weil das höchste Bit 0 ist.

Dh wenn Datei als UTF-8 interpretiert wird:

fgetc(c);

if c  < 128, 0x80, then ASCII
if c == 194, 0xC2, then one more byte follow, interpret to symbol
if c == 226, 0xE2, then two more byte follows, interpret to symbol
...

Als Beispiel. Wenn wir uns einen der von Ihnen erwähnten Charaktere ansehen. Wenn in einem UTF-8-Terminal:

$ echo -n “č” | xxd

Sollte ergeben:

0000000: c48d ..

Mit anderen Worten, “č” wird durch das dargestellt zwei Bytes 0xc4 und 0x8d. Fügen Sie -b zum xxd-Befehl hinzu und wir erhalten die binäre Darstellung der Bytes. Wir zerlegen sie wie folgt:

 ___  byte 1 ___     ___ byte 2 ___                       
|               |   |              |
0xc4 : 1100 0100    0x8d : 1000 1101
       |                    |
       |                    +-- all "follow" bytes starts with 10, rest: 00 1101
       |
       + 11 -> 2 bits set = two byte symbol, the "bits set" sequence
               end with 0. (here 3 bits are used 110) : rest 0 0100

Rest bits combined: xxx0 0100 xx00 1101 => 00100001101
                       \____/   \_____/
                         |        |
                         |        +--- From last byte
                         +------------ From first byte

Das gibt uns: 00100001101 2 = 26910 = 0x10D => Codepunkt entschlüsseln U+010D == “č”.

Diese Nummer kann auch in HTML als verwendet werden &#269; == è

Gemeinsam für dieses und viele andere Codesysteme ist, dass ein 8-Bit-Byte die Basis ist.


Oft ist es auch eine Frage des Kontextes. Nehmen Sie als Beispiel GSM-SMS mit ETSI GSM 03.38/03.40 (3GPP-TS 23.038, 3GPP 23038). Dort finden wir auch eine 7-Bit-Zeichentabelle, 7-Bit-GSM-Standardalphabet, aber anstatt sie als 8 Bit zu speichern, werden sie als 7 Bit gespeichert1. Auf diese Weise können Sie mehr Zeichen in eine bestimmte Anzahl von Bytes packen. Dh aus Standard SMS 160 Zeichen werden 1280 Bit oder 160 Byte als ASCII und 1120 oder 140 Byte als SMS.

1 Nicht ausnahmslos (es kommt eher auf die Story an).

Ein einfaches Beispiel für Bytes, die als Septette (7 Bit) C8329BFD06 im SMS-UDP-Format in ASCII gespeichert werden:

                                _________
7 bit UDP represented          |         +--- Alphas has same bits as ASCII
as 8 bit hex                   '0.......'
C8329BFDBEBEE56C32               1100100 d * Prev last 6 bits + pp 1
 | | | | | | | | +- 00 110010 -> 1101100 l * Prev last 7 bits 
 | | | | | | | +--- 0 1101100 -> 1110010 r * Prev 7 + 0 bits
 | | | | | | +----- 1110010 1 -> 1101111 o * Last 1 + prev 6
 | | | | | +------- 101111 10 -> 1010111 W * Last 2 + prev 5
 | | | | +--------- 10111 110 -> 1101111 o * Last 3 + prev 4
 | | | +----------- 1111 1101 -> 1101100 l * Last 4 + prev 3
 | | +------------- 100 11011 -> 1101100 l * Last 5 + prev 2
 | +--------------- 00 110010 -> 1100101 e * Last 6 + prev 1
 +----------------- 1 1001000 -> 1001000 H * Last 7 bits
                                 '------'
                                    |
                                    +----- GSM Table as binary

Und 9 Byte “unverpackt” wird 10 Zeichen.

  • Dieser Artikel ist einfach toll! Danke für die Zusammenfassung und Übersicht.

    – Miroslav Stuten

    5. April 2012 um 18:07 Uhr

  • @Mimars; Wurde ein bisschen lang, aber, :). Es ist ein interessantes Thema und es macht Spaß zu sehen, wie die Dinge gelöst wurden. Finde es auch lehrreich, dass man beim Codieren eine ähnliche Logik anwenden kann – auch ganz andere Dinge. Es gibt auch einige Schönheiten mit ASCII und wie alles angeordnet und sortiert ist – dh: pp3 hier faculty.kfupm.edu.sa/ics/said/ics232Lectures/…. – Es ist auch lehrreich, sich z. B. /usr/include/ctype.h usw. anzusehen.

    – Morphfh

    5. April 2012 um 19:08 Uhr


ASCII ist 7 Bit, nicht 8 Bit. a char [] enthält Bytes, die in jeder Codierung vorliegen können – iso8859-1, utf-8, was immer Sie wollen. C ist es egal.

Das ist die Magie von UTF-8, dass Sie sich nicht einmal Gedanken darüber machen mussten, wie es funktioniert. Das einzige Problem ist, dass der C-Datentyp benannt wird char (zum Charakter), während es eigentlich bedeutet Byte. Es gibt keine 1:1-Entsprechung zwischen Zeichen und den Bytes, die sie codieren.

Was in Ihrem Code passiert, ist, dass Sie aus Sicht des Programms eine Sequenz von eingeben Byte, speichert es die Bytes im Speicher und wenn Sie den Text drucken, druckt es Bytes. Diesem Code ist es egal, wie diese Bytes die Zeichen codieren, es ist nur das Terminal, das sich darum kümmern muss, sie bei der Eingabe zu codieren und sie bei der Ausgabe richtig zu interpretieren.

Es gibt natürlich viele Bibliotheken, die diese Aufgabe erledigen, aber um schnell jeden UTF8-Unicode zu dekodieren, ist diese kleine Funktion praktisch:

typedef unsigned char utf8_t;

#define isunicode(c) (((c)&0xc0)==0xc0)

int utf8_decode(const char *str,int *i) {
    const utf8_t *s = (const utf8_t *)str; // Use unsigned chars
    int u = *s,l = 1;
    if(isunicode(u)) {
        int a = (u&0x20)? ((u&0x10)? ((u&0x08)? ((u&0x04)? 6 : 5) : 4) : 3) : 2;
        if(a<6 || !(u&0x02)) {
            int b,p = 0;
            u = ((u<<(a+1))&0xff)>>(a+1);
            for(b=1; b<a; ++b)
                u = (u<<6)|(s[l++]&0x3f);
        }
    }
    if(i) *i += l;
    return u;
}

In Anbetracht Ihres Codes; Sie können die Zeichenfolge iterieren und die Unicode-Werte lesen:

int l;
for(i=0; i<20 && input[i]!='\0'; ) {
   if(!isunicode(input[i])) i++;
   else {
      l = 0;
      z = utf8_decode(&input[i],&l);
      printf("Unicode value at %d is U+%04X and it\'s %d bytes.\n",i,z,l);
      i += l;
   }
}

  • Scheint so, als wäre das dann vollständig portabel … keine Bibliothek erforderlich?

    – Arch Stanton

    11. Dezember 2020 um 23:29 Uhr

  • Nun, nicht ganz, auf 16-Bit-Systemen wäre int 16 Bit, und die Funktion erfordert, dass int mindestens 32 Bit hat. Sie könnten entweder long verwenden oder stdint einschließen und beispielsweise uint32_t verwenden, aber in den meisten Fällen reicht int aus, und die Beispielfunktion soll zeigen, wie UTF8 decodiert wird, und daher gibt es Raum für Verbesserungen.

    – Per Löwgren

    20. Dezember 2020 um 13:41 Uhr

Benutzer-Avatar
Greg

Es gibt einen Datentyp wint_t (#include <wchar.h>) für Nicht-ASCII-Zeichen. Sie können die Methode verwenden getwchar() sie zu lesen.

  • Scheint so, als wäre das dann vollständig portabel … keine Bibliothek erforderlich?

    – Arch Stanton

    11. Dezember 2020 um 23:29 Uhr

  • Nun, nicht ganz, auf 16-Bit-Systemen wäre int 16 Bit, und die Funktion erfordert, dass int mindestens 32 Bit hat. Sie könnten entweder long verwenden oder stdint einschließen und beispielsweise uint32_t verwenden, aber in den meisten Fällen reicht int aus, und die Beispielfunktion soll zeigen, wie UTF8 decodiert wird, und daher gibt es Raum für Verbesserungen.

    – Per Löwgren

    20. Dezember 2020 um 13:41 Uhr

1369650cookie-checkUnicode in C-Zeichen gespeichert

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy