C Programmation: comment programmer pour Unicode?

quelles sont les conditions préalables nécessaires pour faire une programmation Unicode stricte?

cela signifie-t-il que mon code ne doit pas utiliser les types char et que des fonctions doivent être utilisées pour traiter wint_t et wchar_t ?

et quel est le rôle joué par les séquences de caractères multibytes dans ce scénario?

76
demandé sur Jonathan Leffler 2009-02-09 00:22:15

8 réponses

notez qu'il ne s'agit pas de "programmation unicode stricte" en soi, mais d'une certaine expérience pratique.

ce que nous avons fait à mon entreprise était de créer une bibliothèque enveloppante autour de la bibliothèque de L'Unité de soins intensifs D'IBM. La bibliothèque wrapper a une interface UTF-8 et convertit en UTF-16 quand il est nécessaire d'appeler ICU. Dans notre cas, nous ne nous sommes pas trop inquiétés des performances. Lorsque la performance était un problème, nous avons également fourni des interfaces UTF-16 (en utilisant notre propre type de données).

Les Applications

pourraient demeurer en grande partie telles quelles (à l'aide de l'OMB), bien que dans certains cas, elles doivent être au courant de certaines questions. Par exemple, au lieu de strncpy (), nous utilisons un wrapper qui évite de couper les séquences UTF-8. Dans notre cas, c'est suffisant, mais on pourrait aussi envisager des vérifications pour combiner les caractères. Nous avons aussi des enveloppes pour compter le nombre de codépoints, le nombre de graphèmes, etc.

Lors de l'interfaçage avec d'autres systèmes, nous avons parfois besoin de faire composition sur mesure des caractères, donc vous pouvez avoir besoin d'une certaine flexibilité (en fonction de votre application).

nous n'utilisons pas wchar_t. L'utilisation de L'Unité de soins intensifs évite les problèmes inattendus de portabilité (mais pas les autres problèmes inattendus, bien sûr :-).

20
répondu Hans van Eck 2009-02-08 22:44:06

C99 ou avant

la norme C (C99) prévoit des caractères larges et des caractères à plusieurs octets, mais comme il n'existe aucune garantie quant à ce que ces caractères larges peuvent contenir, leur valeur est quelque peu limitée. Pour une implémentation donnée, ils fournissent un support utile, mais si votre code doit pouvoir passer d'une implémentation à l'autre, il n'y a pas de garantie qu'ils seront utiles.

par conséquent, L'approche suggérée par Hans van Eck (qui est d'écrire un wrapper autour de L'ICU - composants internationaux pour Unicode - Bibliothèque) est le son, IMO.

l'encodage UTF-8 présente de nombreux avantages, dont l'un est que si vous ne modifiez pas les données (en les tronquant, par exemple), elles peuvent être copiées par des fonctions qui ne sont pas pleinement conscientes des subtilités de l'encodage UTF-8. Ce n'est absolument pas le cas avec wchar_t .

Unicode in full est un format de 21 bits. C'est, Unicode points de code de réserves U+0000 à U+10FF.

une des choses utiles sur les formats UTF-8, UTF-16 et UTF-32 (où UTF représente le Format de Transformation Unicode - voir Unicode ) est que vous pouvez convertir entre les trois représentations sans perte d'information. Chacun peut représenter tout ce que les autres peuvent représenter. L'UTF-8 et L'UTF-16 sont des formats multi-octets.

UTF-8 est bien connu pour être un format multi-octets, avec une structure soigneuse qui permet de trouver de manière fiable le début des caractères dans une chaîne, à partir de n'importe quel point de la chaîne. Les caractères à un octet ont le High-bit mis à zéro. Les caractères à plusieurs octets ont le premier caractère commençant par l'un des motifs de bits 110, 1110 ou 11110 (pour les caractères à 2 octets, 3 octets ou 4 octets), les octets suivants commençant toujours par 10. Les caractères de continuation sont toujours dans la gamme 0x80 .. 0xBF. Il y a des règles que l'UTF-8 les caractères doivent être représentés dans le format minimal possible. Une conséquence de ces règles est que les octets 0xC0 et 0xC1 (aussi 0xF5..0xFF) ne peut apparaître dans les données UTF-8 valides.

 U+0000 ..   U+007F  1 byte   0xxx xxxx
 U+0080 ..   U+07FF  2 bytes  110x xxxx   10xx xxxx
 U+0800 ..   U+FFFF  3 bytes  1110 xxxx   10xx xxxx   10xx xxxx
U+10000 .. U+10FFFF  4 bytes  1111 0xxx   10xx xxxx   10xx xxxx   10xx xxxx

à l'origine, on espérait Qu'Unicode serait un ensemble de code 16 bits et que tout s'insérerait dans un espace de code 16 bits. Malheureusement, le monde réel est plus complexe, et il a fallu l'étendre à l'encodage 21 bits actuel.

UTF-16 est donc un simple code Unité (mot de 16 bits) défini pour le "plan de base multilingue", c'est-à-dire les caractères dont les points de code Unicode sont U+0000 .. U+FFFF, mais utilise deux unités (32 bits) pour les caractères en dehors de cette plage. Ainsi, le code qui fonctionne avec L'encodage UTF-16 doit être capable de gérer des encodages de largeur variable, tout comme le doit UTF-8. Les codes des caractères à deux unités sont appelés substituts.

Les substituts

sont des points de code provenant de deux plages spéciales de valeurs Unicode., réservé à L'utilisation comme valeurs de début et de fin des unités de code appariées en UTF-16. Les substituts principaux, également appelés high, sont de U+D800 à U+DBFF, et les substituts secondaires, ou low, sont de U+DC00 à U+DFFF. Ils sont appelés substituts, car ils ne représentent pas directement les caractères, mais seulement une paire.

UTF-32, bien sûr, peut encoder n'importe quel point de code Unicode dans une seule unité de stockage. Il est efficace pour les calculs, mais pas pour le stockage.

vous pouvez trouver beaucoup plus d'informations sur les sites Web ICU et Unicode.

C11 et <uchar.h>

la norme C11 a modifié les règles, mais toutes les mises en œuvre n'ont pas rattrapé les changements même maintenant (mi-2017). La norme C11 résume les changements pour le support Unicode comme suit:

  • Unicode des caractères et chaînes de caractères ( <uchar.h> ) (initialement spécifié dans ISO / IEC TR 19769: 2004)

ce qui suit est un simple aperçu minimal de la fonctionnalité. La spécification comprend:

6.4.3 noms de caractères universels

Syntaxe

universelle-personnage-nom:

     \u hex-quad

     \U hex-quad hex-quad

hex-quad:

     hexadécimal chiffres hexadécimaux chiffres hexadécimal-chiffre hexadécimal

7.28 Unicode utilitaires <uchar.h>

L'en-tête <uchar.h> déclare types et de fonctions permettant de manipuler Unicode caractère.

les types déclarés sont mbstate_t (décrit au 7.29.1) et size_t (décrit au 7.19);

char16_t

, qui est un type entier non signé utilisé pour les caractères de 16 bits et qui est le même type que uint_least16_t (décrit au 7.20.1.2); et

char32_t

qui est un type entier non signé utilisé pour les caractères 32 bits et est le même type que uint_least32_t (également décrit dans 7.20.1.2).

(traduction des références croisées: <stddef.h> définit size_t , <wchar.h> définit mbstate_t , et <stdint.h> définit uint_least16_t et uint_least32_t .) L'en-tête <uchar.h> définit également un ensemble minimal de fonctions de conversion (restartable):

  • mbrtoc16()
  • c16rtomb()
  • mbrtoc32()
  • c32rtomb()

il existe des règles selon lesquelles les caractères Unicode peuvent être utilisés dans les identificateurs en utilisant les notations \unnnn ou \U00nnnnnn . Vous devrez peut-être activer activement le support de tels caractères dans les identificateurs. Par exemple, GCC exige -fextended-identifiers pour permettre ces dans les identificateurs.

notez que macOS Sierra (10.12.5), pour ne citer qu'une plateforme, ne supporte pas <uchar.h> .

36
répondu Jonathan Leffler 2017-08-11 02:26:22

Ce FAQ est une mine d'info. Entre cette page et cet article de Joel Spolsky , vous aurez un bon départ.

une conclusion à laquelle je suis arrivé en chemin:

  • wchar_t est de 16 bits sur Windows, mais pas nécessairement 16 bits sur d'autres plateformes. Je pense que C'est un mal nécessaire sur les fenêtres, mais peut probablement être évité ailleurs. La raison pour laquelle c'est important sur Windows est que vous avez besoin d'utiliser des fichiers qui ont des caractères non-ASCII dans le nom (avec la version W des fonctions).

  • notez que les API Windows qui prennent des chaînes wchar_t s'attendent à un encodage UTF-16. Notez également que ceci est différent de UCS-2. Prenez note des paires de mères porteuses. Ce page test a des tests éclairants.

  • si vous programmez sur Windows, vous ne peut pas utiliser fopen() , fread() , fwrite() , etc. puisqu'ils ne prennent que char * et ne comprennent pas l'encodage UTF-8. Rend la portabilité douloureuse.

10
répondu dbyron 2018-06-22 13:34:39
"151960920 De" faire le strict programmation Unicode:

  • n'utilisez que des API de chaîne de caractères qui sont Unicode aware ( NOT strlen , strcpy , ... mais leurs contreparties larges wstrlen , wsstrcpy ,...)
  • pour traiter un bloc de texte, utilisez un encodage qui permet de stocker des caractères Unicode (utf-7, utf-8, utf-16, ucs-2, ...) sans perte.
  • vérifiez que votre système d'exploitation est par défaut le jeu de caractères Unicode (ex: utf-8)
  • utiliser des polices compatibles Unicode (par exemple arial_unicode)

Multi-byte character sequences est un encodage qui date d'avant L'encodage UTF-16 (celui utilisé normalement avec wchar_t ) et il me semble qu'il est plutôt Windows-seulement.

je n'ai jamais entendu parler de wint_t .

7
répondu sebastien 2016-12-27 01:07:50

la chose la plus importante est de toujours faire une distinction claire entre le texte et les données binaires . Essayez de suivre le modèle de Python 3.x str vs. bytes or SQL TEXT vs. BLOB .

malheureusement, C confond la question en utilisant char pour "ASCII character" et int_least8_t . Vous voudrez faire quelque chose comme:

typedef char UTF8; // for code units of UTF-8 strings
typedef unsigned char BYTE; // for binary data

vous pourriez vous voulez des typedefs pour les unités de code UTF-16 et UTF-32 aussi, mais c'est plus compliqué parce que l'encodage de wchar_t n'est pas défini. Vous aurez besoin d'un préprocesseur #if s. Quelques macros utiles en C et c++0x sont:

  • __STDC_UTF_16__ - Si défini, le type _Char16_t existe et est UTF-16.
  • __STDC_UTF_32__ - si défini, le type _Char32_t existe et est UTF-32.
  • __STDC_ISO_10646__ - si défini, alors wchar_t est UTF-32.
  • _WIN32 - sur les fenêtres, wchar_t est UTF-16, même si cela va à l'encontre de la norme.
  • WCHAR_MAX - peut être utilisé pour déterminer la taille de wchar_t , mais pas si L'OS l'utilise pour représenter Unicode.

implique-t-il que mon code devrait ne pas utiliser les types de char n'importe où et que il faut utiliser des fonctions qui peuvent traiter avec wint_t et wchar_t?

voir aussi:

Pas de. UTF-8 est un encodage Unicode parfaitement valide qui utilise des chaînes char* . Il a l'avantage que si votre programme est transparent aux octets non-ASCII (par exemple, une fin de ligne convertisseur qui agit sur \r et \n mais passe par d'autres caractères inchangés), vous aurez besoin de faire aucun changement du tout!

si vous allez avec UTF-8, vous aurez besoin de changer toutes les hypothèses que char = caractère (par exemple, ne pas appeler toupper dans une boucle) ou char = colonne d'écran (par exemple, pour le texte enveloppant).

si vous allez avec UTF-32, vous aurez la simplicité des caractères Largeur fixe (mais pas Largeur fixe) graphemes , mais devra changer le type de toutes vos chaînes).

si vous allez avec UTF-16, Vous devrez rejeter à la fois l'hypothèse des caractères de largeur fixe et l'hypothèse des unités de code 8 bits, ce qui rend ce chemin de mise à niveau le plus difficile à partir de l'encodage d'un seul octet.

je recommande activement éviter wchar_t parce que ce n'est pas multiplate-forme: parfois UTF-32, parfois UTF-16, et parfois son encodage pré-Unicode en Asie de l'est. Je recommande d'utiliser typedefs

encore plus important, éviter TCHAR .

3
répondu dan04 2017-05-23 12:17:33

vous voulez essentiellement traiter avec des chaînes en mémoire comme des tableaux wchar_t au lieu de char. Quand vous faites n'importe quel type d'E/S (comme lire/écrire des fichiers) vous pouvez encoder/décoder en utilisant UTF-8 (c'est probablement l'encodage le plus commun) qui est assez simple à mettre en œuvre. Google juste les RFC. Donc en mémoire, rien ne devrait être multi-octets. Un wchar_t représente un caractère. Quand vous venez à sérialiser cependant, c'est quand vous avez besoin de coder quelque chose comme UTF-8 où certains caractères sont représenté par plusieurs octets.

vous devrez également écrire de nouvelles versions de strcmp, etc. pour les larges cordes de caractères, mais ce n'est pas un gros problème. Le plus gros problème sera l'interop avec les bibliothèques / code existant qui n'acceptent que les tableaux de caractères.

et quand il s'agit de sizeof(wchar_t) (vous aurez besoin de 4 bytes si vous voulez le faire correctement) vous pouvez toujours le redéfinir à une plus grande taille avec typedef/macro hacks si vous avez besoin.

2
répondu Mike Weller 2009-02-09 06:40:42

Je ne ferais confiance à aucune implémentation de bibliothèque standard. Il suffit de lancer vos propres types unicode.

#include <windows.h>

typedef unsigned char utf8_t;
typedef unsigned short utf16_t;
typedef unsigned long utf32_t;

int main ( int argc, char *argv[] )
{
  int msgBoxId;
  utf16_t lpText[] = { 0x03B1, 0x0009, 0x03B2, 0x0009, 0x03B3, 0x0009, 0x03B4, 0x0000 };
  utf16_t lpCaption[] = L"Greek Characters";
  unsigned int uType = MB_OK;
  msgBoxId = MessageBoxW( NULL, lpText, lpCaption, uType );
  return 0;
}
2
répondu 2017-03-29 18:45:44

de ce que je sais, wchar_t dépend de l'implémentation (comme le montre cet article du wiki ). Et ce n'est pas unicode.

1
répondu PolyThinker 2009-02-09 06:03:11