WChars, encodages, normes et portabilité

Les éléments suivants peuvent ne pas être considérés comme une question SO; si elle est hors limites, n’hésitez pas à me dire de partir. La question est la suivante: “Ai-je bien compris la norme C et est-ce la bonne façon de faire les choses?”

Je voudrais demander des précisions, des confirmations et des corrections sur ma compréhension de la gestion des caractères en C (et donc en C ++ et C ++ 0x). Tout d’abord, une observation importante:

La portabilité et la sérialisation sont des concepts orthogonaux.

Les choses portables sont des choses comme C, unsigned int , wchar_t . Les choses sérialisables sont des choses comme uint32_t ou UTF-8. “Portable” signifie que vous pouvez recomstackr la même source et obtenir un résultat fonctionnel sur chaque plate-forme supscope, mais la représentation binary peut être totalement différente (ou même inexistante, par exemple pigeon TCP-over-carrier). Les fichiers sérialisables, par contre, ont toujours la même représentation, par exemple le fichier PNG que je peux lire sur mon bureau Windows, sur mon téléphone ou sur ma brosse à dents. Les objects portables sont internes, les choses sérialisables concernent les E / S. Les objects portables sont des types, les objects sérialisables ont besoin d’être punis.

En ce qui concerne la gestion des caractères en C, il existe deux groupes d’éléments liés respectivement à la portabilité et à la sérialisation:

  • wchar_t , setlocale() , mbsrtowcs() / wcsrtombs() : le standard C ne dit rien sur les “encodages” ; En fait, il est totalement indépendant de toute propriété de texte ou de codage. Cela dit seulement “votre point d’entrée est main(int, char**) ; vous obtenez un type wchar_t qui peut contenir tous les caractères de votre système; vous obtenez des fonctions pour lire les séquences de caractères et

  • iconv() et UTF-8,16,32: Une fonction / bibliothèque pour transcoder entre des encodages bien définis, définis et fixes. Tous les encodages traités par iconv sont universellement compris et acceptés, à une exception près.

Le pont entre le monde portable de codage-agnostique de C et son type de caractère portable wchar_t et le monde extérieur déterministe est la conversion iconv entre WCHAR-T et UTF .

Alors, devrais-je toujours stocker mes chaînes en interne dans un wssortingng indépendant du codage, interfacer avec le CRT via wcsrtombs() et utiliser iconv() pour la sérialisation? Conceptuellement:

  my program  CRT | wchar_t[] |  --- mbstowcs --> \==============/ <-- iconv(WCHAR_T, UTF8) --- | +-- iconv(WCHAR_T, UCS-4) --+ | ... <--- (adv. Unicode malarkey) ----- libicu ---+ 

En pratique, cela signifie que j’écrirais deux enveloppes de plaque chauffante pour mon point d’entrée de programme, par exemple pour C ++:

 // Portable wmain()-wrapper #include  #include  #include  #include  std::vector parse(int argc, char * argv[]); // use mbsrtowcs etc int wmain(const std::vector args); // user starts here #if defined(_WIN32) || defined(WIN32) #include  extern "C" int main() { setlocale(LC_CTYPE, ""); int argc; wchar_t * const * const argv = CommandLineToArgvW(GetCommandLineW(), &argc); return wmain(std::vector(argv, argv + argc)); } #else extern "C" int main(int argc, char * argv[]) { setlocale(LC_CTYPE, ""); return wmain(parse(argc, argv)); } #endif // Serialization utilities #include  typedef std::basic_ssortingng U16Ssortingng; typedef std::basic_ssortingng U32Ssortingng; U16Ssortingng toUTF16(std::wssortingng s); U32Ssortingng toUTF32(std::wssortingng s); /* ... */ 

Est-ce la bonne façon d’écrire un kernel de programme idiomatique, portable, universel et indépendant du codage en utilisant uniquement le C / C ++ standard, avec une interface d’E / S bien définie à UTF utilisant iconv? (Notez que les problèmes tels que la normalisation Unicode ou le remplacement diacritique ne sont pas abordés; une fois que vous avez décidé d’opter pour Unicode (par opposition à tout autre système de codage), est-il temps de gérer ces spécificités? comme libicu.)

Mises à jour

Après de très bons commentaires, j’aimerais append quelques observations:

  • Si votre application veut explicitement gérer le texte Unicode, vous iconv définir la partie iconv -conversion du kernel et utiliser uint32_t / char32_t -ssortingngs en interne avec UCS-4.

  • Windows: bien que l’utilisation de chaînes larges soit généralement correcte, il semble que l’interaction avec la console (toute console, d’ailleurs) est limitée, car il ne semble pas y avoir de codage multi-octets sensible et mbstowcs est essentiellement inutile ( autre que pour un élargissement sortingvial). Recevoir des arguments de type large-ssortingng à partir, par exemple, d’un explorateur-drop avec GetCommandLineW + GetCommandLineW fonctionne (il devrait peut-être y avoir un wrapper distinct pour Windows).

  • Systèmes de fichiers: Les systèmes de fichiers ne semblent pas avoir de notion de codage et prennent simplement toute chaîne terminée par un caractère nul comme nom de fichier. La plupart des systèmes prennent des chaînes d’octets, mais Windows / NTFS utilise des chaînes de 16 bits. Vous devez faire attention lors de la découverte des fichiers existants et lors du traitement de ces données (par char16_t séquences char16_t qui ne constituent pas des UTF16 valides (par exemple des substituts nus) sont des noms de fichiers NTFS valides). Le standard C fopen n’est pas capable d’ouvrir tous les fichiers NTFS, car il n’y a pas de conversion possible pour mapper toutes les chaînes 16 bits possibles. L’utilisation de _wfopen spécifique à _wfopen peut être requirejse. En corollaire, il n’ya en général aucune notion bien définie de “combien de caractères” comprend un nom de fichier donné, car il n’ya pas de notion de “caractère” en premier lieu. Caveat emptor.

    Est-ce la bonne façon d’écrire un kernel de programme idiomatique, portable, universel et indépendant du codage en utilisant uniquement le C / C ++ standard?

    Non, et il n’y a aucun moyen de remplir toutes ces propriétés, du moins si vous voulez que votre programme s’exécute sous Windows. Sous Windows, vous devez ignorer presque partout les normes C et C ++ et travailler exclusivement avec wchar_t (pas nécessairement en interne, mais à toutes les interfaces du système). Par exemple, si vous commencez avec

     int main(int argc, char** argv) 

    vous avez déjà perdu le support Unicode pour les arguments de ligne de commande. Il faut écrire

     int wmain(int argc, wchar_t** argv) 

    à la place, ou utilisez la fonction GetCommandLineW , dont aucune n’est spécifiée dans le standard C.

    Plus précisement,

    • tout programme compatible avec Unicode sous Windows doit ignorer activement le standard C et C ++ pour des éléments tels que les arguments de ligne de commande, les E / S de fichiers et de consoles ou la manipulation de fichiers et de répertoires. Ce n’est certainement pas idiomatique . Utilisez les extensions ou les encapsuleurs Microsoft tels que Boost.Filesystem ou Qt à la place.
    • La portabilité est extrêmement difficile à atteindre, en particulier pour le support Unicode. Vous devez vraiment être prêt à savoir que tout ce que vous pensez savoir est peut-être faux. Par exemple, vous devez considérer que les noms de fichiers que vous utilisez pour ouvrir des fichiers peuvent être différents des noms de fichiers réellement utilisés et que deux noms de fichiers apparemment différents peuvent représenter le même fichier. Après avoir créé deux fichiers a et b , vous pouvez vous retrouver avec un seul fichier c , ou deux fichiers d et e , dont les noms de fichiers sont différents des noms de fichiers que vous avez transmis au système d’exploitation. Soit vous avez besoin d’une bibliothèque de wrapper externe ou de lots de #ifdef s.
    • Habituellement, l’ encodage de l’agnosticité ne fonctionne pas, surtout si vous souhaitez être portable. Vous devez savoir que wchar_t est une unité de code UTF-16 sous Windows et que ce caractère est souvent (bot pas toujours) une unité de code UTF-8 sous Linux. La sensibilisation à l’encodage est souvent l’objective le plus souhaitable: assurez-vous de toujours savoir avec quel codage vous travaillez ou utilisez une bibliothèque d’encapsuleurs qui les élimine.

    Je pense que je dois conclure qu’il est complètement impossible de créer une application portable compatible avec Unicode en C ou C ++, à moins que vous ne souhaitiez utiliser des bibliothèques supplémentaires et des extensions spécifiques au système et y consacrer beaucoup d’efforts. Malheureusement, la plupart des applications échouent déjà à des tâches relativement simples telles que “écrire des caractères grecs dans la console” ou “prendre en charge correctement les noms de fichiers autorisés par le système”.

    Je voudrais éviter le type wchar_t car il est dépendant de la plate-forme (pas “sérialisable” selon votre définition): UTF-16 sous Windows et UTF-32 sur la plupart des systèmes de type Unix. Au lieu de cela, utilisez les char16_t et / ou char32_t de C ++ 0x / C1x. (Si vous ne possédez pas de nouveau compilateur, saisissez-les comme uint16_t et uint32_t pour le moment.)

    DOIT définir des fonctions pour convertir entre les fonctions UTF-8, UTF-16 et UTF-32.

    N’écrivez PAS des versions étroites / larges surchargées de chaque fonction de chaîne, comme l’a fait l’API Windows avec -A et -W. Choisissez un encodage préféré à utiliser en interne et respectez-le. Pour les choses qui nécessitent un encodage différent, convertissez-les si nécessaire.

    Le problème avec wchar_t est que le traitement de texte wchar_t codage est trop difficile et devrait être évité. Si vous vous en tenez au “C pur” comme vous le dites, vous pouvez utiliser toutes les fonctions w* telles que wcscat et friends, mais si vous voulez faire quelque chose de plus sophistiqué, vous devez plonger dans l’abîme.

    Voici quelques choses beaucoup plus difficiles avec wchar_t que si vous choisissez un des encodages UTF:

    • Analyser Javascript: Les identificateurs peuvent contenir certains caractères en dehors du BMP (et supposons que vous vous souciez de ce type de correction).

    • HTML: Comment allez-vous 𐀀 dans une chaîne de wchar_t ?

    • Editeur de texte: comment trouvez-vous les limites du cluster grapheme dans une chaîne wchar_t ?

    Si je connais l’encodage d’une chaîne, je peux directement examiner les caractères. Si je ne connais pas l’encodage, je dois espérer que tout ce que je veux faire avec une chaîne est implémenté par une fonction de bibliothèque quelque part. La portabilité de wchar_t est donc quelque peu hors de propos car je ne la considère pas comme un type de données particulièrement utile .

    Vos exigences en matière de programme peuvent différer et wchar_t peut fonctionner correctement pour vous.

    Étant donné que iconv n’est pas “pure standard C / C ++”, je ne pense pas que vous satisfassiez vos propres spécifications.

    Il existe de nouvelles facettes de char32_t avec char32_t et char16_t donc je ne vois pas comment vous pouvez vous tromper tant que vous êtes cohérent et choisissez un type de caractère + encodage si les facettes sont là.

    Les facettes sont décrites dans 22.5 [locale.stdcvt] (à partir de n3242).


    Je ne comprends pas comment cela ne satisfait pas au moins certaines de vos exigences:

     namespace ns { typedef char32_t char_t; using std::u32ssortingng; // or use user-defined literal #define LIT u32 // Communicate with interface0, which wants utf-8 // This type doesn't need to be public at all; I just refactored it. typedef std::wssortingng_convert, char_T> converter0; inline std::ssortingng to_interface0(ssortingng const& s) { return converter0().to_bytes(s); } inline ssortingng from_interface0(std::ssortingng const& s) { return converter0().from_bytes(s); } // Communitate with interface1, which wants utf-16 // Doesn't have to be public either typedef std::wssortingng_convert, char_T> converter1; inline std::wssortingng to_interface0(ssortingng const& s) { return converter1().to_bytes(s); } inline ssortingng from_interface0(std::wssortingng const& s) { return converter1().from_bytes(s); } } // ns 

    Ensuite, votre code peut utiliser ns::ssortingng , ns::char_t , LIT'A' & LIT"Hello, World!" avec un abandon imprudent, sans savoir quelle est la représentation sous-jacente. Ensuite, utilisez from_interfaceX(some_ssortingng) chaque fois que cela est nécessaire. Cela n’affecte pas non plus l’environnement local ou les stream. Les helpers peuvent être aussi intelligents que nécessaire, par exemple codecvt_utf8 peut traiter des «en-têtes», ce que je suppose être un standard de choses délicates comme la nomenclature (idem codecvt_utf16 ).

    En fait, j’ai écrit ce qui précède pour être le plus court possible mais vous voudriez vraiment des aides comme ceci:

     template inline ns::ssortingng ns::from_interface0(T&&... t) { return converter0().from_bytes(std::forward(t)...); } 

    qui vous donne access aux 3 surcharges pour chaque membre [from|to]_bytes , acceptant des choses comme par exemple const char* ou range.