19 Stimmen

C++ Visual Studio Zeichencodierungsprobleme

Nicht in der Lage zu sein, das zu verstehen, ist eine echte Schande...

Ich arbeite mit einer französischen Version von Visual Studio (2008) in einem französischen Windows (XP). Französische Akzente, die in Zeichenfolgen an das Ausgabefenster gesendet werden, werden beschädigt. Gleiches gilt für die Eingabe aus dem Ausgabefenster. Typisches Zeichencodierungsproblem. Ich gebe ANSI ein, erhalte UTF-8 zurück oder etwas in der Art. Welche Einstellung kann sicherstellen, dass die Zeichen in ANSI bleiben, wenn eine "fest codierte" Zeichenfolge im Ausgabefenster angezeigt wird?

EDIT:

Beispiel:

#include 

int main()
{
std::cout << "àéêù" << std::endl;

return 0;
}

Wird im Ausgabefenster angezeigt:

óúÛ¨

(hier als HTML für Ihr Sehvergnügen encodiert)

Ich würde gerne möchten, dass es stattdessen so angezeigt wird:

àéêù

0 Stimmen

Can you give us a little bit more input. Is this happening for build output, all output or something else? Can you give us a specific operation for which this happens (build, debugging, etc ...)

0 Stimmen

Ja, bitte zeigen Sie ein Beispiel dafür, was Ihrer Meinung nach erscheinen sollte und was tatsächlich erscheint.

1 Stimmen

Was passiert, wenn Sie wcout verwenden?

18voto

Bahbar Punkte 17300

Bevor ich weiter gehe, sollte ich erwähnen, dass das, was du tust, nicht c/c++-konform ist. Die Spezifikation besagt in 2.2, welche Zeichensätze im Quellcode gültig sind. Es ist nicht viel drin, und alle verwendeten Zeichen sind im ASCII. Also... Alles Weitere bezieht sich auf eine spezifische Implementierung (genauer gesagt, VC2008 auf einer US-Lokale Maschine).

Um anzufangen, du hast 4 Zeichen in deiner cout-Zeile und 4 Glyphen auf der Ausgabe. Das Problem ist also nicht die UTF8-Codierung, da sie mehrere Quellenzeichen zu weniger Glyphen zusammenführen würde.

Von deiner Quellzeichenfolge bis zur Anzeige auf der Konsole spielen all diese Dinge eine Rolle:

  1. In welcher Codierung sich deine Quelldatei befindet (d.h. wie deine C++-Datei vom Compiler gesehen wird)
  2. Was dein Compiler mit einem Zeichenliteral macht und welche Quellcodierung er versteht
  3. Wie dein << die codierte Zeichenfolge interpretiert, die du übergibst
  4. Welche Codierung die Konsole erwartet
  5. Wie die Konsole diese Ausgabe in ein Schriftzeichen übersetzt.

Jetzt...

1 und 2 sind ziemlich einfache Dinge. Es sieht so aus, als ob der Compiler errät, in welchem Format die Quelldatei liegt, und decodiert sie in seine interne Darstellung. Er generiert den den entsprechenden Datenfragment des Zeichenliteral im aktuellen Codepage, egal welche Quellcodierung vorlag. Ich habe keine expliziten Details/Kontrolle darüber gefunden.

3 ist noch einfacher. Außer Steuerzeichen übergibt << einfach die Daten als char * weiter.

4 wird durch SetConsoleOutputCP kontrolliert. Es sollte standardmäßig auf deine Systems-Codepage eingestellt sein. Du kannst auch herausfinden, welche du hast mit GetConsoleOutputCP (die Eingabe wird anders gesteuert, durch SetConsoleCP)

5 ist eine lustige. Ich habe mir den Kopf zerbrochen, um herauszufinden, warum das é nicht richtig angezeigt wird, wenn ich CP1252 (westeuropäisch, Windows) verwende. Es stellte sich heraus, dass meine System-Schriftart das Glyphen für diesen Zeichen nicht hat und freundlicherweise das Glyphen meiner Standardcodepage verwendet (großes Theta, das Gleiche, das ich erhalten würde, wenn ich nicht SetConsoleOutputCP aufrufen würde). Um es zu beheben, musste ich die Schriftart, die ich in Konsolen verwende, zu Lucida Console ändern (eine TrueType-Schriftart).

Einige interessante Dinge, die ich bei diesem Blick gelernt habe:

  • Die Codierung der Quelle ist egal, solange der Compiler sie herausfinden kann (insbesondere hat das Ändern auf UTF8 nicht den generierten Code geändert. Mein "é" String war immer noch mit CP1252 als 233 0 codiert)
  • VC wählt eine Codepage für die Zeichenliteralen aus, die ich anscheinend nicht kontrollieren kann.
  • Die Kontrolle darüber, was die Konsole anzeigt, ist schmerzhafter als ich erwartet habe

Also... was bedeutet das für dich? Hier sind ein paar Tipps:

  • Verwende keine nicht-ascii in Zeichenliteralen. Verwende Ressourcen, wo du die Codierung kontrollierst.
  • Stelle sicher, dass du weißt, welche Codierung von deiner Konsole erwartet wird und dass deine Schriftart die Glyphen hat, um die Zeichen darzustellen, die du sendest.
  • Wenn du herausfinden möchtest, welche Codierung in deinem Fall verwendet wird, würde ich empfehlen, den tatsächlichen Wert des Zeichens als Ganzzahl auszudrucken. char * a = "é"; std::cout << (unsigned int) (unsigned char) a[0] zeigt bei mir 233 an, was zufällig die Codierung in CP1252 ist.

Übrigens, wenn das, was du bekommen hast, "ÓÚ" anstatt dessen war, was du eingefügt hast, dann scheinen deine 4 Bytes irgendwo als CP850 interpretiert zu werden.

0 Stimmen

Unter Verwendung von Ressourcen.. Auf jeden Fall muss man sich das genauer ansehen. Hier wird es jedoch schwieriger: Die Konsole fungiert gewissermaßen als Filter, denn wenn ich "cin>>" einige Akzentbuchstaben eingebe, werden lustige Zeichen auf der anderen Seite erhalten! Ich bin gerade nicht an diesem Computer, aber ich werde versuchen, was ich von cin bekomme, erneut auszugeben und sehen, ob es weiterhin durcheinandergebracht wird oder ob es sich zurückverwandelt.

0 Stimmen

Ausgezeichnete Antwort. Ich werde dies auf jeden Fall notieren.

0 Stimmen

Diese Antwort ist ziemlich nützlich, um zu verstehen, was mit den Rohbytes der Quellcodedatei für ein Zeichenliteral während des Kompilierungsprozesses und bis zum Laufzeitsystem geschieht. Vielleicht könnten Sie sich stackoverflow.com/questions/27871124/… ansehen?

11voto

Davislor Punkte 13072

Weil ich gebeten wurde, werde ich etwas Nekromantie machen. Die anderen Antworten stammen aus dem Jahr 2009, aber dieser Artikel wird immer noch bei einer Suche angezeigt, die ich 2018 gemacht habe. Die Situation heute ist sehr unterschiedlich. Auch die akzeptierte Antwort war schon 2009 unvollständig.

Der Quellzeichensatz

Jeder Compiler (einschließlich Microsofts Visual Studio 2008 und später, gcc, clang und icc) wird UTF-8-Quelldateien mit BOM ohne Probleme lesen, und clang wird nichts anderes als UTF-8 lesen, daher ist UTF-8 mit BOM das kleinste gemeinsame Vielfache für C- und C++-Quelldateien.

Der Sprachstandard sagt nicht, welche Zeichencodierungen der Compiler unterstützen muss. Einige real existierende Quelldateien sind sogar in einem Zeichensatz gespeichert, der nicht mit ASCII kompatibel ist. Microsoft Visual C++ unterstützte 2008 UTF-8-Quelldateien mit einer Byte-Reihenfolge-Markierung sowie beide Formen von UTF-16. Ohne Byte-Reihenfolge-Markierung würde es annehmen, dass die Datei im aktuellen 8-Bit-Zeichensatz codiert ist, der immer eine Erweiterung von ASCII war.

Die Ausführungszeichensätze

Im Jahr 2012 fügte der Compiler einen /utf-8-Schalter zu CL.EXE hinzu. Heute unterstützt er auch die Schalter /source-charset und /execution-charset sowie /validate-charset, um festzustellen, ob Ihre Datei tatsächlich nicht UTF-8 ist. Diese Seite auf MSDN enthält einen Link zur Dokumentation zur Unicode-Unterstützung für jede Version von Visual C++.

Aktuelle Versionen des C++-Standards besagen, dass der Compiler sowohl einen Ausführungszeichensatz haben muss, der den numerischen Wert von Zeichenkonstanten wie 'a' bestimmt, als auch einen Ausführungszeichensatz für Breitzeichen, der den Wert von Breitzeichenkonstanten wie L'é' bestimmt.

Etwas gesetzestreue Sprache für einen Moment, es gibt sehr wenige Anforderungen im Standard, wie diese codiert sein müssen, und doch schaffen es Visual C und C++, dagegen zu verstoßen. Es muss etwa 100 Zeichen enthalten, die keine negativen Werte haben können, und die Codierungen der Ziffern '0' bis '9' müssen aufeinanderfolgend sein. Weder Groß- noch Kleinbuchstaben müssen es sein, da sie auf einigen alten Großrechnern nicht waren. (Das heißt, '0'+9 muss dasselbe wie '9' sein, aber es gibt immer noch einen Compiler, der heute in der realen Welt verwendet wird, bei dem das Standardverhalten ist, dass 'a'+9 nicht 'j' ist, sondern '«', und das ist legal.) Der Breitzeichen-Ausführungssatz muss den grundlegenden Ausführungssatz enthalten und genügend Bits haben, um alle Zeichen jeder unterstützten Ländereinstellung zu speichern. Jeder gängige Compiler unterstützt mindestens eine Unicode-Ländereinstellung und versteht gültige Unicode-Zeichen, die mit \Uxxxxxxxx angegeben sind, aber ein Compiler, der dies nicht tut, könnte behaupten, dem Standard zu entsprechen.

Visual C und C++ verletzen den Sprachstandard, indem sie ihr wchar_t als UTF-16 definieren, das nur einige Zeichen als Ersatzpaare darstellen kann, obwohl der Standard besagt, dass wchar_t eine festbreite Codierung sein muss. Das liegt daran, dass Microsoft das wchar_t in den 1990er Jahren als 16 Bits breit definiert hat, bevor das Unicode-Komitee feststellte, dass 16 Bits nicht für die gesamte Welt ausreichen würden und Microsoft die Windows-API nicht ändern wollte. Es unterstützt auch den standardmäßigen char32_t-Typ.

UTF-8-Zeichenkettenliterale

Das dritte Problem, das diese Frage aufwirft, ist, wie man den Compiler dazu bringt, einen Zeichenkettenliteral im Speicher als UTF-8 zu codieren. Seit C++11 können Sie etwas wie folgt schreiben:

constexpr unsigned char hola_utf8[] = u8"¡Hola, mundo!";

Dies codiert die Zeichenkette als ihre mit einem Nullzeichen terminierte UTF-8-Byte-Repräsentation, unabhängig davon, ob der Quellzeichensatz UTF-8, UTF-16, Latin-1, CP1252 oder sogar IBM EBCDIC 1047 ist (was ein lächerliches theoretisches Beispiel ist, aber dennoch aus Gründen der Abwärtskompatibilität auf dem IBM-Z-Series-Großrechner-Compiler voreingestellt ist). Das heißt, es ist gleichwertig mit der Initialisierung des Arrays mit { 0xC2, 0xA1, 'H', /* ... , */ '!', 0 }.

Wenn es zu umständlich wäre, ein Zeichen einzugeben, oder wenn Sie zwischen oberflächlich identischen Zeichen wie Leerzeichen und geschützten Leerzeichen oder vorkomponierten und kombinierten Zeichen unterscheiden möchten, haben Sie auch universelle Zeichenescapes:

constexpr unsigned char hola_utf8[] = u8"\u00a1Hola, mundo!";

Sie können diese unabhängig vom Quellzeichensatz und unabhängig davon, ob Sie das Literal als UTF-8, UTF-16 oder UCS-4 speichern, verwenden. Sie wurden ursprünglich in C99 hinzugefügt, aber Microsoft unterstützte sie in Visual Studio 2015.

Bearbeitung: Wie von Matthew berichtet, sind u8"-Zeichenfolgen in einigen Versionen von MSVC fehlerhaft, einschließlich 19.14. Es stellt sich heraus, auch literale Nicht-ASCII-Zeichen, auch wenn Sie /utf-8 oder /source-charset:utf-8 /execution-charset:utf-8 angeben. Der obige Beispielcode funktioniert ordnungsgemäß in 19.22.27905.

Es gibt noch einen anderen Weg, dies zu tun, der in Visual C oder C++ 2008 funktionierte: Oktal- und Hexadezimalescape-Codes. Sie hätten UTF-8-Literale in dieser Version des Compilers so codiert:

const unsigned char hola_utf8[] = "\xC2\xA1Hello, world!";

1 Stimmen

Es scheint, dass du kannst sie UCEs unabhängig vom Quellzeichensatz; VS schlachtet sie in Mojabake sogar für UTF-X Literale. (Andererseits handelt es sich dabei fast sicher um einen Compilerfehler...)

0 Stimmen

@Matthew Der von Ihnen gemeldete Fehler wurde in MSVC 19.22.27905 behoben. Danke!

0 Stimmen

@Matthew Ich habe eine Notiz zur Version des Compilers hinzugefügt, mit der ich den Fehler reproduzieren konnte, und zur Version, die funktioniert hat. Ich wäre jedoch dankbar für weitere Informationen, falls du welche hast.

6voto

ruf Punkte 61

Versuchen Sie es mit:

#include 
#include 

int main()
{
 std::locale::global(std::locale(""));
 std::cout << "àéêù" << std::endl;

 return 0;
}

1 Stimmen

Schön, aber das scheint nur für die Ausgabe zu funktionieren, die Eingabe, die von der Konsole erhalten wird, ist immer noch zufälliges Kauderwelsch.

4voto

Marc.2377 Punkte 6732

Mit _setmode() zu arbeiten ¹ funktioniert und ist möglicherweise besser als das Ändern der Codepage oder das Festlegen einer Lokalität, da es tatsächlich dazu führt, dass Ihr Programm in Unicode ausgibt und somit konsistent ist - unabhängig davon, welche Codepage oder Lokalität gerade eingestellt sind.

Beispiel:

#include 
#include 
#include 

int wmain()
{
    _setmode( _fileno(stdout), _O_U16TEXT );

    std::wcout << L"àéêù" << std::endl;

    return 0;
}

Innerhalb von Visual Studio stellen Sie sicher, dass Sie Ihr Projekt für Unicode einrichten (Rechtsklick auf Projekt -> Klicken auf Allgemein -> Zeichensatz = Unicode-Zeichensatz verwenden).

MinGW Benutzer:

  1. Definieren Sie sowohl UNICODE als auch _UNICODE
  2. Fügen Sie -finput-charset=iso-8859-1 zu den Compiler-Optionen hinzu, um diesen Fehler zu umgehen: "converting to execution character set: Invalid argument"
  3. Fügen Sie -municode zu den Linker-Optionen hinzu, um "undefined reference to `WinMain@16" zu umgehen (weiterlesen).

Bearbeiten: Der entsprechende Aufruf, um den Unicode input einzustellen, lautet: _setmode( _fileno(stdin), _O_U16TEXT );

Bearbeiten 2: Ein wichtiger Hinweis, insbesondere wenn der Code std::cout verwendet. Dies wird nicht unterstützt. Die MSDN-Dokumentation besagt (Hervorhebung von mir):

Der Unicode-Modus ist für breite Druckfunktionen (zum Beispiel wprintf) und wird nicht für schmale Druckfunktionen unterstützt. Die Verwendung einer schmalen Druckfunktion auf einem Unicode-Modus-Stream löst einen assert aus.

Verwenden Sie daher nicht std::cout, wenn der Konsolenausgabemodus _O_U16TEXT lautet; ebenso wenig verwenden Sie std::cin, wenn die Konsoleneingabe _O_U16TEXT ist. Sie müssen die breite Version dieser Funktionen verwenden (std::wcout, std::wcin).
Und beachten Sie, dass es nicht erlaubt ist, das Mixen von cout und wcout in der gleichen Ausgabe zu verwenden (aber ich finde, es funktioniert, wenn Sie flush() aufrufen und dann _setmode() vor dem Wechsel zwischen den schmalen und breiten Operationen aufrufen).

0 Stimmen

@Nikos SetConsoleCP() ist überflüssig, weil es bei Unicode-Eingaben nicht wirklich wichtig ist, welche Codepage verwendet wird. Du kannst mehr über Codepages vs. Unicode in diesem Joel-Beitrag lesen. Überprüfe meine Bearbeitung, um zu sehen, wie man Unicode-Eingaben setzt.

3voto

Charles Anderson Punkte 17827

Ich habe diesen Code ausprobiert:

#include 
#include 
#include 

int main()
{
    std::wstringstream wss;
    wss << L"àéêù";
    std::wstring s = wss.str();
    const wchar_t* p = s.c_str();
    std::wcout << ws.str() << std::endl;

    std::wofstream file("C:\\a.txt");
    file << p << endl;

    return 0;
}

Der Debugger zeigte, dass wss, s und p alle die erwarteten Werte hatten (d. h. "àéêù"), ebenso wie die Ausgabedatei. Jedoch erschien in der Konsole óú.

Das Problem liegt daher an der Visual Studio Konsole, nicht am C++. Mit Bahbars ausgezeichneter Antwort habe ich hinzugefügt:

    SetConsoleOutputCP(1252);

als erste Zeile, und die Konsolenausgabe erschien dann wie sie sollte.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X