Unicode hell (sous Windows) [fermé]
Aujourd'hui, je me suis réveillé et j'ai senti que quelque chose n'allait pas avec mon code et toutes les bibliothèques que j'ai jamais utilisées, et je pense que j'avais raison... (ou veuillez indiquer où mon raisonnement est faux)
Commençons I décennie ou deux dans le temps, tout allait bien dans le monde. J'ai parlé à mon voisin et il parlait la même langue: tout simplement l'anglais. Pour moi, mon voisin et Windows, il semblait évident de stocker notre chaîne en 8 bits char
s parce que tous les caractères que nous avons utilisés pourraient être stockés dans le 2^8=256 combinaisons disponibles.
Puis L'être miraculeux Internet est venu et m'a permis de parler à des amis en Europe (qui n'avaient pas le temps d'apprendre l'anglais). Cela a été difficile avec notre format char
, le nombre de caractères utilisés A facilement dépassé 256, donc dans notre vision tout à fait simpliste, nous avons décidé d'utiliser le wchar_t
16 bits. quelque chose appelé UCS-2 unicode. Il a 2 ^ 16=65.536 combinaisons disponibles et cela doit être suffisant pour toutes les langues du monde! Convaincus de notre exactitude nous avons même ajouté des fonctions 16 bits de L'API Windows W
comme MessageBoxW
et CreateWindowW
. Nous avons convaincu chaque programmeur de notre religion et découragé l'utilisation des homologues 8 bits maléfiques (MessageBoxA
et CreateWindowA
) et mappé un appel à MessageBox
automatiquement à MessageBoxW
en définissant _UNICODE
dans nos builds. Par conséquent, nous devrions également utiliser les fonctions wcs
au lieu des anciennes fonctions str
(par exemple, strlen
devrait maintenant être wcslen
, ou utiliser le _tcslen
automatiquement mappé).
Puis les choses ont mal tourné, il s'est avéré il y avait d'autres personnes dans le monde qui utilisaient des glyphes encore plus étranges (sans offense) que les nôtres: Japonais, Chinois, etc. Il est devenu mauvais parce que par exemple Chinois a plus 70.000 caractères différents. Beaucoup de jurons se sont produits et nous ont laissé un nouveau type d'unicode: UTF-16. Il utilise également un type de données 16 bits, mais certains caractères nécessitent deux valeurs 16 bits (appelées paire de substitution ). Ce qui signifie que nous ne pouvons pas utiliser d'index sur ces chaînes de 16 bits (par exemple, theString[4] peut ne pas renvoyer le 5ème caractère). De patch L'API Windows il a été décidé que toutes les fonctions W
devraient maintenant supporter le format UTF-16, c'était une décision facile puisque toutes les anciennes chaînes UCS-2 étaient également des chaînes UTF-16 valides. Cependant, parce que nous sommes des programmeurs courageux, nous utilisons maintenant les fonctions wcs
. Malheureusement, ces fonctions ne sont pas au courant de Substitution et sont toujours conformes au format UCS-2...
Entre-temps, dans un grenier sombre, une autre forme plus compacte d'unicode a été développée: UTF-8. Utilisation d'un type de données 8 bits la plupart des langues occidentales peuvent être stockés dans une seule valeur de 8 bits, tout comme dans les vieux jours. Lorsqu'un glyphe plus exotique est stocké, plusieurs valeurs 8 bits sont utilisées, pour la plupart des langues européennes, 2 suffira. Cependant, il peut développer 4 de ces valeurs, créant essentiellement un type de stockage 32 bits. Tout comme C'est FAT brother UTF-16, nous ne pouvons pas utiliser d'index sur ces chaînes. En raison de son format plus compact UTF-8 est maintenant largement utilisé partout sur Internet car il économise de la bande passante.
Bien, vous avez réussi à de longues écriture-up :) Maintenant j'ai quelques questions / points d'intérêt:
OK, je suis assez satisfait de L'utilisation de UTF-8 pour le stockage. Quand je lis un fichier (à partir d'un disque ou D'une réponse HTTP), je détecte la signature UTF-8
"xEFxBBxBF"
et mets le contenu à traversMultiByteToWideChar
ce qui me laisse avec une chaîne UTF-16. Je peux l'utiliser avec les fonctions APIW
, Pas de problème. Mais maintenant, je veux modifier la chaîne, remplacer certains caractères etc. Les bonnes vieilles fonctionswcs
ne sont plus bonnes, quel noyau les fonctions de chaîne sont-elles compatibles UTF-16? Ou y a-t-il une splendide bibliothèque là-bas que je ne connais pas? Edit: il semble que ICU soit une très bonne solution. J'ai également trouvé que les fonctionswcs
ne sont pas complètement inutiles, vous pouvez par exemple toujours utiliserwcsstr
pour rechercher, il ne fait que Comparerwchar_t
s. Le seul problème est la longueur de la chaîne.N'avez-vous pas le sentiment qu'une erreur laide a été faite lorsque nous avons été forcés d'utiliser des fonctions
W
déficientes en 16 bits. Le problème ne devrait-il pas avoir été reconnu beaucoup plus tôt et laisser toutes les fonctions API originales prendre des chaînes UTF-8 et intégrer des routines de manipulation de chaînes appropriées? Ou est-ce déjà possible et je suis horriblement trompe? Edit: peut-être que c'était une question stupide, le recul est en effet merveilleux, inutile de mettre quelqu'un en bas en ce moment ;)Pour un accès rapide à l'index des caractères, nous devrions stocker des chaînes dans des valeurs 32 bits. Est-ce normal? (Je peux vous entendre penser: et puis nous avons frappé un langage extraterrestre nécessitant plus de combinaisons et le plaisir recommence...) Il semble que l'inconvénient de cette approche est que nous devrions convertir la chaîne en UTF-16 chaque fois que nous faisons des appels D'API Windows. Edit: juste pour citer Alf P. Steinbach un caractère par index est un rêve désespéré , je le vois maintenant. Une chose que j'ai complètement manqué était le diacritiques . Je pense aussi que c'est une bonne chose à traiter dans le système d'exploitation encodage natif (pour Windows UTF-16). Bien que UTF-8 aurait été un meilleur choix, nous sommes coincés avec UTF-16 maintenant, il ne sert à rien de convertir entre votre code et L'API. Comme suggéré ci-dessous, je vais garder une trace des parties dans une chaîne moi-même par des pointeurs au lieu d'un nombre de caractères.
Je pense que vous méritez vous-même une bonne tasse de thé du mal si cette longue question, allez en chercher un avant de répondre;)
Edit: j'accepte le fait que ma question est fermé, ce serait un meilleur ajustement pour un billet de blog, mais là encore je n'écris pas de blog. Je pense que cette chose d'encodage de caractères est essentielle et devrait être le sujet suivant dans n'importe quel Livre de programmation après le simple exemple de hello world! L'affichage ici attire l'attention de nombreux experts, ces gens ne lisent aucun blog aléatoire et j'apprécie fortement leur opinion. Donc merci à tous pour leur contribution.
6 réponses
Par préférence forte, vous devriez traduire de UTF - * à UCS-4 pendant que vous lisez les données. Tout votre traitement doit être fait sur UCS-4, puis (si nécessaire) traduire en UTF-* pendant la sortie.
Cela ne résout toujours pas tout. Il y a un ensemble de marques "combinant diacritiques", ce qui signifie que même lorsque vous utilisez UCS-4, string[N]
ne correspond pas nécessairement au caractère Nth de la chaîne. Il y a des transformations aux formes canoniques qui tentent d'aider avec cela, mais ils ne peuvent pas toujours faire le travail, donc si c'est vraiment critique (pour votre application), vous devez marcher à travers la chaîne, la diviser en unités qui représentent chacune un caractère complet (caractère de base + et combinaison de diacritiques), et traiter chacune de celles-ci comme une unité.
ICU est une excellente bibliothèque de chaînes Unicode. Le concept général avec la gestion des chaînes est d'analyser toutes les formes externes en mémoire de sorte que chaque valeur est un point de code complet, pas une partie d'un, comme avec UTF-16 et UTF-8. Ensuite, après tout traitement, à la sortie du programme, sérialisez la chaîne dans un format de transformation approprié. Bien que les bases soient faciles, essayez de ne pas lancer votre propre bibliothèque Unicode - des choses comme le collation, la recherche et d'autres les questions compliquées sont mieux laissées à une bibliothèque mature.
Les avions en dehors du BMP n'ont pas été utilisés ni définis, car un besoin n'a pas été vu. Bien sûr, comme vous l'avez souligné, il y a certainement un besoin.
Oui, c'est commun, et comme mentionné, c'est la meilleure façon de faire les choses car elle améliore grandement presque toutes les opérations de chaîne.
Mon point de vue sur la question:
Pour l'interface externe (fichiers, arguments de ligne de commande, variables d'environnement, stdin/out), utilisez UTF-8, car c'est un flux byte et l'ensemble du langage C et c++ est conçu autour de l'interface avec l'environnement via des flux d'octets. Sur la plupart des systèmes de fichiers sensibles, les noms de fichiers sont également des chaînes d'octets (terminées par null).
Pour un retour simple, vous pouvez également conserver les chaînes en UTF-8 en interne, en utilisant
char*
etc., et les littéraux de chaîne""
simples ou les nouveaux littérauxu8""
UTF-8.Pour la manipulation textuelle, convertissez la chaîne en UTC-4/UTF-32 en interne et traitez - la comme un tableau de
char32_t
. C'est la seule façon saine de parler d'un flux caractère.UTF-16 était une énorme erreur et devrait être abattu et boudé. Voir ici (j'ai fait un commentaire il y a quelque part), et peut-être ici et ici.
Je ne sais pas ce que vous voulez dire à propos des fonctions wcs
qui ne sont pas bonnes. Pourquoi pas?
N'avez-vous pas le sentiment qu'une erreur laide a été commise lorsque nous avons été forcés d'utiliser des fonctions W déficientes en 16 bits. Le problème ne devrait-il pas avoir été reconnu beaucoup plus tôt et laisser toutes les fonctions API originales prendre des chaînes UTF-8 et intégrer des routines de manipulation de chaînes appropriées? Ou est-ce déjà possible et je suis horriblement trompe?
UTF-8 a été développé bien après L'interface Windows Unicode a été écrite. S'ils avaient ajouté une version UTF-8, Il y aurait maintenant 3 versions de chaque fonction. Je suis sûr qu'ils n'utiliseraient pas UTF-16 s'ils devaient recommencer-le recul est vraiment merveilleux.
En ce qui concerne UTF-32, presque aucun logiciel ne l'utilise en interne. Je ne le recommanderais pas, surtout pas sur une plate-forme qui n'a aucun support pour cela. Utiliser UTF-32 serait juste créer du travail pour vous-même.
ICU - composants internationaux pour Unicode . Pour les pauses de mots et l'affichage appropriés, Windows inclut Uniscribe {[4] } et les non-Windows utilisent FreeType (corrigez-moi si je me trompe).
Oui. Mais pour autant que je sache, au moment où ils prenaient cette décision, utf-32 n'existait pas et ils pensaient que 65536 points de code "suffiraient à tout le monde".
Non. En plus de quadrupler l'utilisation de la mémoire, le problème est bien pire que vous ne le pensez. Vous vous ne pouvez pas simplement "modifier une chaîne" et "remplacer certains caractères": même en utilisant des valeurs 32 bits, car un caractère unicode ne signifie pas nécessairement une lettre écrite ou un glyphe que vous pouvez supprimer ou remplacer par autre chose et espérer que rien ne se casse. Pour travailler correctement avec du texte, vous devrez utiliser quelque chose comme ICU de toute façon, donc il n'y a pas beaucoup de différence entre l'utilisation de utf-8 et utf-32 je pense.
Rien ne vous empêche de créer un cache simple qui stocke l'emplacement et la longueur d'octet d'un point de code codé en UTF afin que vous puissiez réellement utiliser un accès aléatoire. Tous les vieux trucs C dont vous parlez ne vont pas aider beaucoup cependant.
Je ne voudrais pas non plus faire confiance à la 'nomenclature' UTF-8 étant disponible parce que c'est un non-sens et probablement dépouillé loin par certaines implémentations.