Das traurige Schicksal von Printf-Funktionsformatbezeichnern für Unicode-Zeichen in Visual C ++

Die Unicode-Unterstützung unter Windows wurde früher als unter den meisten anderen Betriebssystemen angezeigt. Aus diesem Grund wurden viele Probleme im Zusammenhang mit der Darstellung von Zeichen in Windows nicht auf die gleiche Weise gelöst wie in anderen Systemen, deren Entwickler die Implementierung des neuen Standards auf bessere Zeiten verschoben haben [1]. Das aussagekräftigste Beispiel: Unter Windows wird die UCS-2-Codierung zur Darstellung von Unicode-Zeichen verwendet. Es wurde vom Unicode-Konsortium empfohlen, da Version 1.0 nur 65.536 Zeichen unterstützt [2]. Fünf Jahre später änderte das Konsortium seine Meinung, aber bis dahin war es zu spät, um etwas in Windows zu ändern, da Win32s, Windows NT 3.1, Windows NT 3.5, Windows NT 3.51 und Windows 95 bereits auf den Markt gebracht worden waren - alle verwendeten UCS-Codierung -2 [3].

Aber heute werden wir über die Formatzeichenfolgen der printf- Funktion sprechen.

Da Unicode unter Windows früher als in C eingeführt wurde, mussten Microsoft-Entwickler herausfinden, wie die Unterstützung für diesen Standard in der C-Laufzeit implementiert werden kann. Infolgedessen wurden Funktionen wie wcscmp , wcschr und wprintf angezeigt . Für die Formatierung von Zeichenfolgen in printf wurden die folgenden Qualifikationsmerkmale eingeführt:

  • % s steht für eine Zeichenfolge mit der gleichen Breite wie die Formatzeichenfolge.
  • % S stellt eine Zeichenfolge dar, deren Breite umgekehrt zur Breite der Formatzeichenfolge ist.
  • % hs stellt eine reguläre Zeichenfolge dar, unabhängig von der Breite der Formatzeichenfolge.
  • % ws und % ls stellen eine breite Zeichenfolge dar, unabhängig von der Breite der Formatzeichenfolge .

Die Idee war, Code wie folgt zu schreiben:

TCHAR buffer[256]; GetSomeString(buffer, 256); _tprintf(TEXT("The string is %s.\n"), buffer); 

Wenn Sie im ANSI-Modus kompilieren, erhalten Sie folgendes Ergebnis:

 char buffer[256]; GetSomeStringA(buffer, 256); printf("The string is %s.\n", buffer); 

Und beim Kompilieren im Unicode-Modus - dies [4]:

 wchar_t buffer[256]; GetSomeStringW(buffer, 256); wprintf(L"The string is %s.\n", buffer); 

Da der % s- Bezeichner eine Zeichenfolge mit der gleichen Breite wie die Formatzeichenfolge akzeptiert, funktioniert dieser Code sowohl im ANSI- als auch im Unicode-Format ordnungsgemäß. Außerdem vereinfacht diese Lösung die Konvertierung von bereits geschriebenem Code vom ANSI-Format in das Unicode-Format erheblich, da der % s- Bezeichner durch die Zeichenfolge mit der erforderlichen Breite ersetzt wird.

Als die Unicode-Unterstützung offiziell zu C99 hinzugefügt wurde, hat das C-Sprachstandardisierungskomitee ein anderes Format-String-Modell für die printf- Funktion übernommen:

  • % s und % hs stellen eine reguläre Zeichenfolge dar;
  • % ls steht für eine breite Zeichenfolge.

Hier begannen die Probleme. In den letzten sechs Jahren wurde eine große Anzahl von Programmen mit einem Volumen von Milliarden von Zeilen für Windows geschrieben, und sie verwendeten das alte Format. Wie werden Visual C- und C ++ - Compiler?

Es wurde beschlossen, das alte, nicht standardmäßige Modell beizubehalten, um nicht alle vorhandenen Windows-Programme der Welt zu beschädigen.

Wenn Sie möchten, dass Ihr Code sowohl in Laufzeitumgebungen funktioniert, die den klassischen Regeln für printf entsprechen, als auch in solchen, die den Regeln des C-Standards entsprechen, müssen Sie sich auf die % hs- Spezifizierer für reguläre Zeichenfolgen und % ls für breite Zeichenfolgen beschränken. In diesem Fall ist die Konstanz der Ergebnisse garantiert, unabhängig davon, ob die Formatzeichenfolge an die Funktion sprintf oder wsprintf übergeben wird .

 #ifdef UNICODE #define TSTRINGWIDTH TEXT("l") #else #define TSTRINGWIDTH TEXT("h") #endif TCHAR buffer[256]; GetSomeString(buffer, 256); _tprintf(TEXT("The string is %") TSTRINGWIDTH TEXT("s\n"), buffer); char buffer[256]; GetSomeStringA(buffer, 256); printf("The string is %hs\n", buffer); wchar_t buffer[256]; GetSomeStringW(buffer, 256); wprintf("The string is %ls\n", buffer); 

Mit einer separaten TSTRINGWIDTH- Definition können Sie beispielsweise diesen Code schreiben:

 _tprintf(TEXT("The string is %10") TSTRINGWIDTH TEXT("s\n"), buffer); 

Da die Leute die tabellarische Darstellung von Informationen mögen, ist hier eine Tabelle für Sie.


Ich habe Zeilen mit Qualifikationsmerkmalen hervorgehoben, die in C genauso definiert sind wie im klassischen Format von Windows [5]. Verwenden Sie diese Qualifikationsmerkmale, wenn Ihr Code in beiden Formaten dieselben Ergebnisse liefern soll.

Anmerkungen

[1] Es scheint, dass die Einführung von Unicode in Windows vor anderen Systemen Microsoft den Vorteil des ersten Schrittes hätte verschaffen sollen, aber - zumindest im Fall von Unicode - wurde es für sie zu einem „Fluch des Pioniers“, da der Rest beschloss, nur bis zu besseren Zeiten zu warten. wenn es vielversprechendere Lösungen geben wird (wie UTF-8-Codierung) und erst danach Unicode in ihre Systeme einführen.

[2] Anscheinend glaubten sie, dass 65.536 Zeichen für alle ausreichen sollten .

[3] Es wurde später durch UTF-16 ersetzt. Glücklicherweise ist UTF-16 für die Codezeichen, die in beiden Codierungen dargestellt werden können, abwärtskompatibel mit UCS-2.

[4] Formal sollte die Unicode-Version folgendermaßen aussehen:

 unsigned short buffer[256]; GetSomeStringW(buffer, 256); wprintf(L"The string is %s.\n", buffer); 

Tatsache ist, dass wchar_t noch kein unabhängiger Typ war und bis es zum Standard hinzugefügt wurde, war es nur ein Synonym für unsigned short . Die Wendungen des Schicksals wchar_t finden Sie in einem separaten Artikel .

[5] Das von Windows entwickelte klassische Format erschien zuerst, daher war es wahrscheinlicher, dass sich der C-Standard daran anpassen musste und nicht umgekehrt.

Anmerkung des Übersetzers

Ich bin dem Autor für diese Veröffentlichung dankbar. Nun wurde klar, wie sich all diese Verwechslung mit "% s" herausstellte. Tatsache ist, dass unsere Benutzer ständig die Frage stellten, warum PVS-Studio anders auf ihren „portablen“ Code reagiert, je nachdem, ob sie ihr Projekt unter Linux oder Windows sammeln. In der Beschreibung der V576- Diagnose zu diesem Thema musste ein separater Abschnitt erstellt werden (siehe "Breite Linien"). Nach diesem Artikel wird alles noch klarer und offensichtlicher. Ich denke, dieser Hinweis sollte jedem vorgelesen werden, der plattformübergreifende Anwendungen entwickelt. Lesen und erzählen Sie Kollegen.

Source: https://habr.com/ru/post/de466875/


All Articles