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.
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.
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 č
== è
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.
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;
}
}
Es gibt einen Datentyp wint_t
(#include <wchar.h>
) für Nicht-ASCII-Zeichen. Sie können die Methode verwenden getwchar()
sie zu lesen.
It’s not really characters, it’s bytes that are gotten with
getchar()
. Every character is encoded as a byte sequence.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: “Привет”.
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 inchar
data type, which should be one byte.Apr 4, 2012 at 18:48
No,
getchar()
doesn’t decode it into bytes. The input buffer from whichgetchar()
reads already contains the possibly several bytes making up the character you typed. Eachgetchar()
gets you one of the bytes, so for UTF-8 encoded input, a character can take up to fourgetchar()
. When you print it out, the byte sequence is sent to the terminal and that translates it into glyphs.Apr 4, 2012 at 18:53
Great, thanks, I completely understand now!
Apr 4, 2012 at 19:03