Le triste sort des spécificateurs de format de fonction printf pour les caractères Unicode dans Visual C ++

La prise en charge Unicode sous Windows est apparue plus tôt que sur la plupart des autres systèmes d'exploitation. Pour cette raison, de nombreux problèmes liés à la représentation des personnages dans Windows n'ont pas été résolus de la même manière que dans d'autres systèmes dont les développeurs ont reporté la mise en œuvre de la nouvelle norme jusqu'à des temps meilleurs [1]. L'exemple le plus révélateur: sous Windows, le codage UCS-2 est utilisé pour représenter les caractères Unicode. Il a été recommandé par le consortium Unicode car la version 1.0 ne supportait que 65 536 caractères [2]. Cinq ans plus tard, le Consortium a changé d'avis, mais il était alors trop tard pour changer quelque chose dans Windows, car Win32s, Windows NT 3.1, Windows NT 3.5, Windows NT 3.51 et Windows 95 avaient déjà été lancés sur le marché - ils utilisaient tous le codage UCS -2 [3].

Mais aujourd'hui, nous allons parler des chaînes de format de la fonction printf .

Étant donné qu'Unicode a été adopté sur Windows plus tôt qu'en C, cela signifiait que les développeurs de Microsoft devaient comprendre comment implémenter la prise en charge de cette norme dans le runtime C. En conséquence, des fonctionnalités telles que wcscmp , wcschr et wprintf sont apparues . En ce qui concerne le formatage des chaînes dans printf , les qualificatifs suivants ont été introduits pour eux:

  • % s représente une chaîne de la même largeur que la chaîne de format;
  • % S représente une chaîne dont la largeur est inverse à la largeur de la chaîne de format;
  • % hs représente une chaîne régulière quelle que soit la largeur de la chaîne de format;
  • % ws et % ls représentent une chaîne large quelle que soit la largeur de la chaîne de format.

L'idée était d'écrire du code comme ceci:

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

Et lors de la compilation en mode ANSI, obtenez ce résultat:

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

Et lors de la compilation en mode Unicode - ceci [4]:

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

Étant donné que le spécificateur % s accepte une chaîne de la même largeur que la chaîne de format, ce code fonctionnera correctement aux formats ANSI et Unicode. De plus, cette solution simplifie considérablement la conversion du code déjà écrit du format ANSI au format Unicode, car la chaîne de la largeur requise remplace le spécificateur % s .

Lorsque le support Unicode a été officiellement ajouté à C99, le comité de normalisation du langage C a adopté un modèle de chaîne de format différent pour la fonction printf :

  • % s et % hs représentent une chaîne régulière;
  • % ls représente une chaîne large.

C'est là que les problèmes ont commencé. Au cours des six dernières années, un grand nombre de programmes avec un volume de milliards de lignes ont été écrits pour Windows et ils utilisaient l'ancien format. Comment être des compilateurs Visual C et C ++?

Il a été décidé de rester sur l'ancien modèle non standard, afin de ne pas casser tous les programmes Windows existants dans le monde.

Si vous voulez que votre code fonctionne dans les deux environnements d'exécution qui respectent les règles classiques pour printf et ceux qui suivent les règles de la norme C, vous devrez vous limiter aux spécificateurs % hs pour les chaînes régulières et % ls pour les chaînes larges. Dans ce cas, la constance des résultats est garantie, que la chaîne de format soit transmise à la fonction sprintf ou wsprintf .

 #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); 

Une définition TSTRINGWIDTH distincte vous permet d'écrire, par exemple, ce code:

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

Étant donné que les gens aiment la présentation tabulaire des informations, voici un tableau pour vous.


J'ai mis en évidence des lignes avec des qualificatifs, qui sont définis en C de la même manière que dans le format classique adopté dans Windows [5]. Utilisez ces qualificatifs si vous souhaitez que votre code produise les mêmes résultats dans les deux formats.

Remarques

[1] Il semblerait que l'introduction d'Unicode dans Windows avant d'autres systèmes devrait donner à Microsoft l'avantage du premier pas, mais - au moins dans le cas d'Unicode - cela s'est transformé en une «malédiction du pionnier» pour eux, parce que les autres ont décidé d'attendre des temps meilleurs, quand il y aura des solutions plus prometteuses (comme le codage UTF-8), et seulement après cela, introduisez Unicode dans leurs systèmes.

[2] Apparemment, ils pensaient que 65 536 caractères auraient dû être suffisants pour tout le monde .

[3] Il a ensuite été remplacé par UTF-16. Heureusement, UTF-16 est rétrocompatible avec UCS-2 pour les caractères de code qui peuvent être représentés dans les deux encodages.

[4] Formellement, la version Unicode devrait ressembler à ceci:

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

Le fait est que wchar_t n'était pas encore un type indépendant, et jusqu'à ce qu'il soit ajouté à la norme, ce n'était qu'un synonyme de short non signé . Les rebondissements du destin wchar_t peuvent être trouvés dans un article séparé .

[5] Le format classique développé par Windows est apparu en premier, il était donc plus probable que la norme C devait s'y adapter, et non l'inverse.

Note du traducteur

Je remercie l'auteur de cette publication. Maintenant, il est devenu clair comment toute cette confusion avec "% s" s'est avérée. Le fait est que nos utilisateurs se sont constamment posé la question de savoir pourquoi PVS-Studio réagit différemment à leur code «portable», selon eux, selon qu'ils collectent leur projet sous Linux ou Windows. Il était nécessaire de créer une section distincte spéciale dans la description des diagnostics V576 consacrés à ce sujet (voir "Lignes larges"). Après cet article, tout devient encore plus clair et évident. Je pense que cette note devrait être lue à tous ceux qui développent des applications multiplateformes. Lisez et informez vos collègues.

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


All Articles