Nicht ASCII-Zeichen von Datei einlesen



  • Hallo,

    ich habe vor kurzem angefangen C zu lernen. Zur Zeit lerne Ich Input/Output-Funktionen. Ich hatte auch keine Probleme Zeichen von einer Text-Datei einzulesen und diese auf der Kommandozeile mit putchar() auszugeben.

    Anschließend habe ich probiert, ob dies auch mit arabischen und hebräischen Schriftzeichen funktioniert. Tatsächlich funktioniert es auch.

    Nun habe ich aber auch probiert, ob man diese Zeichen durch Kontrollstrukturen jagen oder mit Prinf(%d...) ausgeben kann. Und hier liegt das Problem. Die Funktion fgetc() gibt ja einen char wert zurück, der in einen int wert gecastet wurde, welcher dem jew. ASCII-Wert entspricht. Nur eben nicht bei den Schriftzeichen. Hier werden komische lange Zahlenwerte ausgegeben.

    Meine Fragen:

    Was sind das für Zahlenwerte und kann man mit ihnen arbeiten iSv zB Kontrollstrukturen?

    Gibt es eine alternative Funktion, die mit Schriftzeichen klar kommt?

    Gibt es Möglichkeiten in C mit Unicode, UTF-8 etc zu arbeiten?

    Habe gestern Abend 6 Stunden danach im Internet gesucht und nichts spezifisch für C gefunden, sondern nur für andere Sprachen...
    Ich hab mir ein Codebsp. absichtlich gespart.

    Vielen Dank schonmal! 🙂



  • ASCII sind nur 127 Zeichen. Da ist nix mit besonderen Zeichen wie Umlaute oder gar andere Schriftarten dabei.

    Wenn du Zeichen verarbeiten willst, die mehr als ein Byte belegen musst du Funktionen aus wchar.h oder uchar.h nehmen.
    Je nach Codierung.

    Du kannst erstmal bei http://www.cplusplus.com/reference/cwchar/ nachsehen, was es da gibt.
    Ist auch mit Beisielen bei den Funktionen.



  • boarduser schrieb:

    Anschließend habe ich probiert, ob dies auch mit arabischen und hebräischen Schriftzeichen funktioniert. Tatsächlich funktioniert es auch.

    Ich gehe davon aus, dass du Windows verwendest? Microsoft definiert den Widecharacter-Standard auf 16 Bit LE, da sollte bspw. wprintf ohne Probleme funktionieren. Kann ich leider nicht testen, ist hier eine Linux-Maschine mit UTF-8, und die Kiste gibt nur lustige chinesischen Zeichen raus, wenn ich nur daran denke, die Einstellung umzuändern.

    Nur weil M$ 16 Bit für die Enkodierung verwendet, heißt das nicht, dass das überall so ist:

    #include <stdio.h>
    #include <wchar.h>
    
    int main(void)
    {
            printf("sizeof(char): %lu Bit\n",sizeof(char)*8);
            printf("sizeof(wchar_t): %lu Bit\n",sizeof(wchar_t)*8);
            return 0;
    }
    

    Ausgabe:

    sizeof(char): 8 Bit
    sizeof(wchar_t): 32 Bit

    Auf meiner Linux-Box. Bei dir wird es wahrscheinlich 16 Bit für wchar_t sein. Siehe auch.
    Aber jetzt wird's noch schöner: wchar_t ist Teil von C90, und der Standard ist damit älter als ich. Inzwischen kam C11 raus und hat char16_t und char32_t vorgestellt, um "eine unmissverständliche Repräsentation von 16-Bit und 32-BIt-Unicode-Umwandlungsformaten" anzubieten. " wchar_t sei implementierungsabhägig". Tolle Wurst also.

    Was ist denn überhaupt der Unterschied zwischen "normalen Buchstaben" mit char als Typ, und Wide Charactern? Denn auch mit normalen Buchstaben kannst du Sonderzeichen ausgeben:

    #include <stdio.h>
    
    int main(void)
    {
            printf("ÄäÖöÜüß\n");
            return 0;
    }
    

    Ausgabe:

    ÄäÖöÜüß

    Wenn du dieses Ergebnis NICHT rausbekommst, bist du entweder unter Windows und/oder deine Quelltextdatei hat nicht den gleichen Zeichensatz wie deine Ausgabe. Unter Windows hast du auf der Konsole noch eine alte DOS-Codepage (850, glaube ich?), und sonst immer 1252. Mit einem Call von setlocale(LC_CTYPE,"de_DE") vor der Ausgabe solltest du das aber beheben können.
    Zu Codepages: das sind M$-interne Lösungen für die Probleme, die mit Unicode eigentlich gelößt werden sollten, und sind eine weitere, eigentlich unnötige, wegen der Abwärtskompatibilität aber dennoch benötigte Zutate in dem Chaosstrudel namens "Zeichensatz".

    Wenn du dich bis jetzt noch nicht übergeben hast, ist das gut.
    Noch einmal: unter Windows braucht normales Unicode UTF-16, die Zeichen werden also mit 16 Bit kodiert, unter Linux 32 Bit. ABER: die meisten Linuxe verwenden UTF-8.

    *Stöhn* Was ist denn DAT nun schon wieder?

    willst du vielleicht fragen?
    Nun, überlege mal. Normales Unicode auf Linux sind 32 Bit, auf Windows 16. Selbst, wenn es sich dabei gar nicht um ein Sonderzeichen handelt.

    #include <stdio.h>
    #include <stdint.h>
    
    int main(void)
    {
            /*Emuliert, weil mein superneues, modernes Gentoo-Linux immer noch
            **kein char32_t kann.*/
            uint32_t c='a';
    
            /*Hier holen wir uns jedes einzelne Byte dieses Unicode-Zeichens.*/
            uint8_t*b1=(uint8_t*)&c;
            uint8_t*b2=(uint8_t*)&c+1;
            uint8_t*b3=(uint8_t*)&c+2;
            uint8_t*b4=(uint8_t*)&c+3;
    
            /*Und jetzt Byte-weise Ausgabe.*/
            printf("%c|%c|%c|%c\n",*b1,*b2,*b3,*b4);
    
            return 0;
    }
    

    Ausgabe:

    $ ./char
    a|||
    $ ./char | hd # Diesmal wollen wir die Hexwerte haben
    00000000 61 7c 00 7c 00 7c 00 0a |a|.|.|..|
    00000008

    Übersetzt: für ein einfaches 'a' benötigt UTF-32 (Standard-Unicode unter Linux halt) immer noch vier mal mehr Bytes, als eigentlich notwendig sind. Aber dafür kann man dann auch elbisch und klingonisch in seine Strings packen.
    Und für den Normalo-Programmierer hat man dann UTF-8, welches nicht mit festen Bytegrößen arbeitet, sondern mit Steuerzeichen innerhalb des Strings. Ein 'a' benötigt unter UTF-8 nur ein Byte, ein 'ä' zwei Bytes:

    #include <stdio.h>
    #include <stdint.h>
    
    int main(void)
    {
            uint16_t c='ä';
    
            /*Hier holen wir uns jedes einzelne Byte dieses UTF8-Zeichens.*/
            uint8_t*b1=(uint8_t*)&c;
            uint8_t*b2=(uint8_t*)&c+1;
    
            printf("%c%c\n",*b2,*b1);
    
            return 0;
    }
    

    Keinen Plan, ob das Programm bei dir läuft, ich bekomme zwar eine Warnung:

    Warnung: Zeichenkonstante mit mehreren Zeichen [-Wmultichar]

    aber es funzt.
    Ausgabe:

    $ ./char
    ä
    $ ./char | hd # Wieder alles hexadezimal ausgeben lassen
    ./char | hd
    00000000 c3 a4 0a |...|
    00000003

    Das '0a' ist das Newline ("\n"), das kannst du ignorieren. Wichtig ist: Ich teile das 'ä' in zwei Teile, lasse diese separiert auf der Konsole ausgeben, und bekomme wieder das 'ä' raus. Das 'c3' ist das Steuerzeichen hier, welches angibt, dass da gleich noch ein Zeichen kommt.
    Speicherschonender? Ist UTF-8. Schneller? Sind Unicode-Formate mit 16- oder 32-Bits pro Zeichen. Weil man da nicht in den Speicher schauen muss, ob es sich jetzt um ein Sonderzeichen handelt, sondern einfach garantiert hat, dass man alle 2/4 Byte ein neues Zeichen hat.
    Und falls du es bemerkt haben solltest: die Ausgaben sind deswegen verkehrt herum, weil das hier eine Little-Endian-Maschine ist. Fast alles, was man normalerweise so Zuhause stehen hat, ist Little-Endian. Das Gegenteil ist Big-Engian oder auch "Motorola-Reihenfolge". Du erinnerst dich, wie ich zu Anfang sagte, dass M$ Unicode auf "16 Bit LE" standardisiert? Das "LE" steht für Little-Endian.

    Zusammenfassung:
    - Zeichensätze sind scheiße
    - lass da bloß die Finger von, bis du ein bisschen im Programmieren drin steckst. Ich meine das so - ich habe es versucht und Jahrelang kaum verstanden, weil ich nicht genug Erfahrung hatte.

    boarduser schrieb:

    Nun habe ich aber auch probiert, ob man diese Zeichen durch Kontrollstrukturen jagen oder mit Prinf(%d...) ausgeben kann. Und hier liegt das Problem. Die Funktion fgetc() gibt ja einen char wert zurück, der in einen int wert gecastet wurde, welcher dem jew. ASCII-Wert entspricht. Nur eben nicht bei den Schriftzeichen. Hier werden komische lange Zahlenwerte ausgegeben.

    Dann schaue ich mir die Beschreibung von fgetc an:

    man fgetc schrieb:

    fgetc() liest das nächste Zeichen von stream und gibt es als ein unsigned char gecastet in einem int zurück.

    Und frage mich, wie dein Code wohl aussieht. Denn diese Beschreibung sagt mir nicht, was fgetc für ein Zeichen hält. Das nächste Byte, oder beachtet es den jeweiligen Zeichensatz?
    Noch einmal: Zeichensätze sind scheiße.

    boarduser schrieb:

    Meine Fragen:

    Was sind das für Zahlenwerte und kann man mit ihnen arbeiten iSv zB Kontrollstrukturen?

    Gibt es eine alternative Funktion, die mit Schriftzeichen klar kommt?

    Gibt es Möglichkeiten in C mit Unicode, UTF-8 etc zu arbeiten?

    Siehe alles oben. DirkB hat dir ja auch schon die Referenz zukommen lassen.

    boarduser schrieb:

    Habe gestern Abend 6 Stunden danach im Internet gesucht und nichts spezifisch für C gefunden, sondern nur für andere Sprachen...

    Andere Sprachen arbeiten gar nicht auf dem gleichen Level wie C. Wenn ich mir anschaue, was z.B. die Leute in Java für Schmerzen auf sich nehmen, um einfache, über das Netzwerk gesendete Daten in eine Datei zu speichern, wird mir ganz anders.

    boarduser schrieb:

    Ich hab mir ein Codebsp. absichtlich gespart.

    Fehler! Code sagt mehr als tausend Worte - aber nur dann, wenn dein Code selbst nicht tausend Worte beinhaltet. 🙂


  • Mod

    Toller Beitrag 👍 . Aber noch ein Nachtrag: Auch UTF-16 ist eine Kodierung mit variabler Länge. Kommt (in der westlichen Welt) bloß recht selten vor, dass man tatsächlich mal einem Multibytezeichen begegnet, es wird daher oft fälschlich als Format mit fixer Länge angesehen.



  • Ok, ich werde dann mal fürs Erste mit den Funktionen von wchar/uchar experimentieren 🙂

    @Dachschaden: Danke für den ausführlichen Beitrag 👍 Ich benutze übrigens Ubuntu 14.04

    Scheint ja insgesamt keine leichte Materie für einen Anfänger zu sein...


Anmelden zum Antworten