Umlaute in C



  • @Finnegan sagte in Umlaute in C:

    Wenn man einem char-Literal wie in diesem Fall mehrere Zeichen zuweist (z.B. char c = 'ABC';) dann gibt es erstens diese Warnung und was für ein char daraus letztendlich gemacht wird, ist soweit ich weiß "implementation-defined".

    Der Grund ist, dass es damals keinerlei Festlegung gab wie groß ein char sein musste. Mein persönliches historisches Lieblingsbeispiel ist UNICOS. Da ist ein char 64Bit groß. Die Single UNIX Spec definiert später ein charals immer 8Bit groß. Aber das sind Festlegungen außerhalb der ISO C Norm.



  • @john-0 sagte in Umlaute in C:

    Der Grund ist, dass es damals keinerlei Festlegung gab wie groß ein char sein musste. Mein persönliches historisches Lieblingsbeispiel ist UNICOS. Da ist ein char 64Bit groß. Die Single UNIX Spec definiert später ein charals immer 8Bit groß. Aber das sind Festlegungen außerhalb der ISO C Norm.

    Das spielt sicher auch mit rein, aber sollte dann z.B. 'A' nicht eher für einen 64-bit Integer stehen, z.B. 0x0000000000000041? Nach dem Schema für char-Literale mit mehreren Zeichen müsste da auch der Code für entweder das erste oder das letzte Zeichen rauskommen (denke auch noch von Endianness abhängig).

    Ansonsten: Ich hab ich in meinem Beitrag auch noch eine "UTF-8-Umgebung" erwähnt. Mir kommt grad noch in den Sinn, dass z.B. unter Windows die Konsole primär mit UTF-16 arbeitet. Das macht es nochmal etwas komplizierter abzuschätzen, was bei dem Programm wirklich passiert. Eventuell kann man nämlich wirklich kein Zeichen eingeben, bei dem die Bedingung greift (keinen Nerv grad zu überlegen, mit welcher Eingabe man da "tricksen" könnte. Das da oben reicht mir für heute 😉 ).



  • @john-0

    Ich programmiere C unter Linux auf der Konsole, mein Texteditor ist kwrite, der wiederum ist auf UTF-8 eingestellt (mit ISO 8859-15 als Ausweich-Kodierung). Deutsche Sonderzeichen in printf-Ausgaben werden problemlos angezeigt (ob auch das große ẞ habe ich bis jetzt noch nicht ausprobiert).

    Wo lege ich denn die Plattform für das Programm fest? Und wie passe ich die Locales an?



  • @Finnegan sagte in Umlaute in C:

    Das spielt sicher auch mit rein, aber sollte dann z.B. 'A' nicht eher für einen 64-bit Integer stehen, z.B. 0x0000000000000041? Nach dem Schema für char-Literale mit mehreren Zeichen müsste da auch der Code für entweder das erste oder das letzte Zeichen rauskommen (denke auch noch von Endianness abhängig).

    Damals war alles etwas wild. Ich kann Dir nicht sagen wie es exakt war, da ich UNICOS nie selbst genutzt habe.

    Heute funktioniert das unter UNIX/Linux wie folgt.
    Der Sourcecode wird in UTF-8 eingelesen, daher hat man kein Byteorder-Problem. Zeichenkonstanten wie z.B. 'a' werden als 8Bit Zeichen interpretiert. Das kann mit UTF-8 Probleme geben, weil UTF-8 ein variables Byte-Encoding ist, und wenn das dann nicht passt, wird abgeschnitten. Schreibst Du hingegen L'a' in den Sourcecode wird das als wchar_t (32bit) interpretiert und vom Compiler in die passende Byteorder der Zielplattform gebracht.

    Bei Strings sieht die Sache etwas anders aus. Schreibt man da "Ein String mit Umlauten ÄÖÜ", hängt es vom Sourcecode und der locale Einstellung ab, wie das interpretiert wird. Der Voreinstellung ist aktuell, dass der Sourcecode als UTF-8 vorliegen muss, es als UTF-8 interpretiert wird und auch so ins Programm kommt. Früher wurde das als ISO-Latin-1 im Editor abgelegt und vom Compiler auch so ins Programm kodiert. Schreibt man hingegen L"Ein String mit Umlauten ÄÖÜ" wird das zu einem wchar_t String vom Compiler übersetzt (der Source sollte als UTF-8 vorliegen, sonst kommt es zu Fehler bei der Interpretation der Zeichen), und in der Zielplattform Byteorder im Code abgelegt.

    Nimmt man wie hier im Beispiel printf und scanf interpretiert Linux das nun als UTF-8, das Problem hierbei ist, dass die Umlaute als Multibyte vorliegen und so das alte Programm nicht mehr richtig funktioniert. Weder die Umlaute für den Vergleich funktionieren, noch die Eingabe als ein Zeichen funktioniert.

    Ansonsten: Ich hab ich in meinem Beitrag auch noch eine "UTF-8-Umgebung" erwähnt. Mir kommt grad noch in den Sinn, dass z.B. unter Windows die Konsole primär mit UTF-16 arbeitet. Das macht es nochmal etwas komplizierter abzuschätzen, was bei dem Programm wirklich passiert. Eventuell kann man nämlich wirklich kein Zeichen eingeben, bei dem die Bedingung greift (keinen Nerv grad zu überlegen, mit welcher Eingabe man da "tricksen" könnte. Das da oben reicht mir für heute 😉 ).

    Windows nutzt generell UTF-16 für Unicode. Nur mit WSL gibt es die UNIX/Linux Variante.



  • @Yadgar sagte in Umlaute in C:

    Wo lege ich denn die Plattform für das Programm fest? Und wie passe ich die Locales an?

    Da Du hier kein Crosscompiling machst, brauchst Du Dich nicht um das Thema Plattform zu kümmern. Normalerweise setzt man das Locale entweder aus dem Programm heraus oder in dem man vorher in der Shell den Befehl export LC_ALL=de_DE nutzt. Übrigens kannst Du Dir die locale Einstellung über den Befehl locale im Terminal ausgeben lassen.

    Ich konnte das Programm bei mir mit ISO-Latin-1 übersetzen, aber das Programm läuft irgend wie nicht richtig, da er trotz gesetztem Locale weiterhin das nicht korrekt interpretiert. 😞 Mal sehen, ob ich das morgen irgend wie auf die Reihe bekomme.



  • So diese Variante mit Wide Versionen der Standard Calls funktioniert bei mir. (Es ist aber C99.)

    #define _POSIX_C_SOURCE 200112L
    
    #include <stdio.h>
    #include <ctype.h>
    #include <stdlib.h>
    #include <string.h>
    #include <wchar.h>
    #include <wctype.h>
    #include <locale.h>
    
    int main() {
        wchar_t eingabe, ausgabe;
        setlocale(LC_ALL, "de_DE.utf8");
    
        wprintf(L"\n%lc\n", eingabe);
        wprintf(L"\nGeben Sie ein deutsches Sonderzeichen als Großbuchstabe ein (Umlaut oder ẞ): ");
        wscanf (L"%lc", &eingabe);
    
        if (!(eingabe == L'Ä' || eingabe == L'Ö' || eingabe== L'Ü' || eingabe == L'ẞ')) {
            wprintf(L"Dies war leider kein passendes Zeichen!\n");
        } else {
            ausgabe = towlower(eingabe);
            wprintf(L"\nIhre Eingabe %lc als Kleinbuchstabe: %lc\n", eingabe, ausgabe);
        }
    
        return EXIT_SUCCESS;
    }
    


  • @john-0 sagte in Umlaute in C:

    So diese Variante mit Wide Versionen der Standard Calls funktioniert bei mir. (Es ist aber C99.)

    ...das heißt, C90 kennt noch keine wide chars?



  • @Yadgar
    Wiki weiß dazu:

    The C and C++ standard libraries include a number of facilities for dealing with wide characters and strings composed of them. The wide characters are defined using datatype wchar_t, which in the original C90 standard was defined as

    "an integral type whose range of values can represent distinct codes for all members of the largest extended character set specified among the supported locales" (ISO 9899:1990 §4.1.5)

    Aber der Header "wchar.h" ist wohl erst mit C95 in die Standard Library aufgenommen worden.



  • Mal wieder eine zu lange Antwort geschrieben. Die wichtigste Punkt sind hervorgehoben.

    @Yadgar sagte in Umlaute in C:

    ...das heißt, C90 kennt noch keine wide chars?

    Es gab damals noch keine einheitlich Norm, wie man Zeichen außerhalb der Plattform spezifischen 8Bit Zeichensätzen umgeht. Jede Plattform hat damals ihr eigenes Süppchen gekocht. Selbst US-ASCII ist nicht die einzige Norm für eine US-Kodierung, denn es gibt das ältere EBCDIC, was durch IBMs Mainframes starke Verbreitung fand. Typisch für die Heim-/Personalcompurer der 1980er ist, dass die oberen 128 Kodierungen für graphische Symbole bzw. Sonderzeichen genutzt wurden: siehe Codepage 437, Codepage 850, PETSCII, … .

    In den späten 1970er/frühen 1980er hat DEC in den Terminals bereits einen Vorläufer von Latin-1 unterstützt. Die DEC Terminals waren der de facto Standard, den alle anderen kopiert haben. Bei einigen Modellen gab es noch zusätzliche Kodierungen (andere Latin Varianten und immer eine Grafikkodierung). Des erklärt auch, weshalb unter UNIX die Grafikzeichen nicht in den eigentlichen Zeichensatz kodiert wurden. Wenn Du auf dem Terminal z.B. Rahmen zeichnen willst, schaltete man auf den Grafikzeichensatz um. Passende Erklärungen dazu kann man in der Beschreibung der curses Library finden, oder man nutzt nun wcurses und gibt die Codes für die Grafiksymbole aus Unicode an.

    Nach längerem Anlauf wurde 1987 dann ISO 8859-1 verabschiedet. UNIX setzt seitdem auf ISO 8859-1 und ISO 6429, die beide zusammen das ältere US-ASCII erweitern, das ist auch seitdem die Standard C Kodierung von UNIX, auf die das System bei Fehler zurückfällt. Die anderen ISO 8859 Versionen kamen etwas danach und definieren für alle europäischen Sprachen, Hebräisch, Arabisch (vereinfacht), Thai, … die Kodierungen. Für CJK gab es schon wchar_t, aber in der Literatur aus dieser Zeit ist das Thema mit einigen kurzen Sätzen abgehandelt. Wahrscheinlich muss man dazu die japanische Literatur nutzen, um das nachlesen zu können.

    1991 kam die erste Unicode Version heraus. Erst ab diesem Zeitpunkt war ein einheitlicher Weg für alle Plattformen absehbar, so dass in den Programmiersprachen auch Verfahren eingeführt wurden mit Unicode umzugehen. Der wichtige Punkt ist, dass die untersten 8Bit von Unicode der ISO 8859-1/ISO 6429 Kodierung entsprechen.

    Nachtrag:
    Erst in C90 wurde wchar_t eingeführt. Davor war das auch proprietär.



  • Letzte Meldung: *john 0s Workaround mit den wide_chars funktioniert auch unter C90! Danke für den Tipp!


Anmelden zum Antworten