59 votes

WChars, codages, normes et portabilité

Les éléments suivants peuvent ne pas être considérées comme une question; si c'est en dehors des limites, sentez-vous svp libre pour me dire d'aller loin. La question ici est en fait, "Dois-je comprendre le C standard correctement et est-ce la bonne façon de faire les choses?"

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

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

Portable les choses sont des choses comme C, unsigned int, wchar_t. Serializable choses sont des choses comme uint32_t ou UTF-8. "Portable" signifie que vous pouvez recompiler la même source, et obtenir un résultat sur toutes les plateformes prises en charge, mais la représentation binaire peut être totalement différente (ou même ne pas exister, par exemple TCP-sur-pigeon voyageur). Serializable choses sur l'autre main toujours avoir 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. Portable les choses sont à l'intérieur, serializable choses traiter avec I/O. Portable, les choses sont typesafe, serializable choses besoin de saisir beaucoup les jeux de mots. </préambule>

Quand il s'agit de la gestion des caractères en C, il y a deux groupes de choses liées respectivement à la portabilité et de la sérialisation:

  • wchar_t, setlocale(), mbsrtowcs()/wcsrtombs(): La norme ne dit rien à propos de "encodages"; en fait, il est tout à fait agnostique à un texte ou d'encodage des propriétés. Il est dit que "votre point d'entrée est - main(int, char**); vous obtenez un type wchar_t qui peut contenir tous vos système de caractères; vous obtenir des fonctions pour lire l'entrée char-séquences et de faire d'eux réalisable wstrings et vice versa.

  • iconv() et UTF-8,16,32: UNE fonction/bibliothèque de transcodage entre bien défini, précis, fixe les codages. Tous les encodages traitées par iconv sont universellement comprise et acceptée, à une exception près.

Le pont entre le portable, l'encodage monde agnostique de C avec ses wchar_t portable type de caractère et la déterministe monde extérieur est iconv la conversion entre les WCHAR-T et UTF.

Donc, dois-je toujours stocker mes chaînes en interne dans un encodage-agnostique wstring, interface avec le CRT via wcsrtombs(), et l'utilisation iconv() pour la sérialisation? Sur le plan conceptuel:

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

Pratiquement, cela signifie que j'écrirais deux de la chaudière-plaque de wrappers pour mon point d'entrée du programme, par exemple pour le C++:

// Portable wmain()-wrapper
#include <clocale>
#include <cwchar>
#include <string>
#include <vector>

std::vector<std::wstring> parse(int argc, char * argv[]); // use mbsrtowcs etc

int wmain(const std::vector<std::wstring> args); // user starts here

#if defined(_WIN32) || defined(WIN32)
#include <windows.h>
extern "C" int main()
{
  setlocale(LC_CTYPE, "");
  int argc;
  wchar_t * const * const argv = CommandLineToArgvW(GetCommandLineW(), &argc);
  return wmain(std::vector<std::wstring>(argv, argv + argc));
}
#else
extern "C" int main(int argc, char * argv[])
{
  setlocale(LC_CTYPE, "");
  return wmain(parse(argc, argv));
}
#endif
// Serialization utilities

#include <iconv.h>

typedef std::basic_string<uint16_t> U16String;
typedef std::basic_string<uint32_t> U32String;

U16String toUTF16(std::wstring s);
U32String toUTF32(std::wstring s);

/* ... */

Est-ce la bonne façon d'écrire un idiomatiques, portable, universel, codage indépendant du programme de base en utilisant uniquement pur standard C/C++, avec un plan bien défini interface d'e/S de l'UTF utiliser iconv? (À noter que des questions telles que la normalisation Unicode ou diacritique de remplacement sont en dehors de la portée; qu'après vous décidez que vous voulez réellement Unicode (par opposition à tout autre système de codage que vous pourriez fantaisie), il est temps de traiter avec ces détails, par exemple à l'aide d'une bibliothèque dédiée comme libicu.)

Les mises à jour

Suite à de nombreux très bons commentaires, je voudrais ajouter quelques observations:

  • Si votre demande explicitement veut traiter avec le texte Unicode, vous devez faire l' iconv-conversion d'une partie de la base et de l'utilisation uint32_t/char32_t-chaînes en interne avec UCS-4.

  • Windows: lors de l'utilisation large de chaînes de caractères est généralement bon, il semble que l'interaction avec la console (console, d'ailleurs) est limitée, car il ne semble pas être prise en charge pour n'importe quelle multi-octets console de codage et d' mbstowcs est essentiellement inutile (autres que pour trivial élargissement). La réception de l'échelle-les arguments de chaîne à partir de, disons, un Explorateur-goutte avec GetCommandLineW+CommandLineToArgvW fonctionne (il faudrait peut-être séparée de l'enveloppe pour Windows).

  • Systèmes de fichiers des systèmes de Fichiers ne semblent pas avoir la moindre notion de codage et il suffit de prendre l'chaîne nul comme nom de fichier. La plupart des systèmes de chaînes d'octets, mais Windows/NTFS prend de 16 chaînes de bits. Vous devez prendre soin lors de la découverte de fichiers qui existent et lors de la manipulation des données (par exemple, char16_t des séquences qui ne constituent pas valide UTF16 (p. ex. nus mères porteuses) sont valables les noms de fichiers NTFS). La Norme C fopen n'est pas capable d'ouvrir tous les fichiers NTFS, car il n'y a pas de conversion possible que l'ensemble des 16-bits cordes. L'utilisation de la spécifiques à Windows _wfopen peut être nécessaire. Comme corollaire, il n'y a en général pas bien défini la notion de "nombre de caractères" constitue une donnée de nom de fichier, car il n'y a pas de notion de "personnage" dans la première place. Caveat emptor.

21voto

Philipp Points 21479

Est-ce la bonne façon d'écrire un idiomatiques, portable, universel, codage indépendant du programme de base en utilisant uniquement pur standard C/C++

Non, et il n'y a aucun moyen de s'acquitter de toutes ces propriétés, au moins si vous voulez que votre programme fonctionne sur Windows. Sur Windows, vous devez ignorer le C et le C++ normes, presque partout, et de travailler exclusivement avec des 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 de l'Unicode pour les arguments de ligne de commande. Vous devez écrire

int wmain(int argc, wchar_t** argv)

au lieu de cela, ou de l'utilisation de l' GetCommandLineW fonction, aucun n'est spécifié dans la norme.

Plus précisément,

  • tout compatibles Unicode programme sur Windows doivent participer activement à ignorer le C et le C++ standard pour des choses comme les arguments de ligne de commande, fichier et la console I/O, ou le fichier et le répertoire de la manipulation. Ce n'est certainement pas idiomatique. Utiliser les extensions de Microsoft ou wrappers comme Boost.Système de fichiers ou de Qt à la place.
  • La portabilité est extrêmement difficile à réaliser, notamment pour la prise en charge Unicode. Vous devez vraiment être préparé que tout ce que vous pensez que vous savez est peut-être faux. Par exemple, vous avez à considérer que les noms de fichier que vous utilisez pour ouvrir les fichiers peuvent être différents à partir des fichiers qui sont effectivement utilisés, et que les deux apparemment des noms de fichiers différents peuvent représenter le même fichier. Après avoir créé deux fichiers a et b, vous pourriez vous retrouver avec un seul fichier c, ou deux fichiers d et e, dont les noms de fichiers sont différents des noms de fichier que vous avez passé à l'OS. Soit vous avez besoin d'un wrapper de la bibliothèque ou des lots d' #ifdefs.
  • Encodage agnosticity habituellement juste ne fonctionne pas dans la pratique, surtout si vous souhaitez être portable. Vous devez savoir qu' wchar_t est un code UTF-16 unité sur Windows et qu' char est souvent bot (pas toujours) un UTF-8 code de l'unité sur Linux. Encodage de la prise de conscience est souvent la plus souhaitable objectif: assurez-vous de toujours savoir à qui l'encodage vous de travail, ou utiliser un wrapper de la bibliothèque que les résumés.

Je pense que je dois en conclure qu'il est totalement impossible de construire un portable application compatibles Unicode en C ou C++, sauf si vous êtes prêt à utiliser d'autres bibliothèques et les extensions spécifiques, et de mettre beaucoup d'effort. Malheureusement, la plupart des applications n'ont pas à relativement simple des tâches telles que la "écrit en caractères grecs à la console" ou "supportant tout nom de fichier autorisés par le système d'une manière correcte", et de telles tâches ne sont que les premiers petits pas vers une véritable prise en charge d'Unicode.

9voto

dan04 Points 33306

Je voudrais éviter de l' wchar_t type de car il dépend de la plateforme (et non pas "serializable" par votre définition): UTF-16 sur Windows et UTF-32 sur la plupart des systèmes de type Unix. Au lieu de cela, utilisez l' char16_t et/ou char32_t types de C++0x/C1x. (Si vous ne disposez pas d'un nouveau compilateur, typedef comme uint16_t et uint32_t pour l'instant.)

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

NE PAS écrire surchargé étroit/large versions de chaque fonction de chaîne comme l'API Windows ne avec -Un et -W. Choisir un encodage préféré d'utiliser en interne, et de s'y tenir. Pour les choses qui ont besoin d'un autre encodage, convertir que nécessaire.

8voto

Dietrich Epp Points 72865

Le problème avec wchar_t , c'est que le codage indépendant du traitement de texte est trop difficile et doit être évitée. Si vous restez avec "pure C" comme vous le dites, vous pouvez utiliser tous les de la w* des fonctions comme wcscat et les amis, mais si vous voulez faire quelque chose de plus sophistiqué, alors vous devez vous plonger dans l'abîme.

Voici quelques choses que beaucoup plus difficile avec wchar_t qu'ils sont, si vous venez de ramasser l'un des codages UTF:

  • L'analyse Javascript: Identificateurs peuvent contenir certains caractères en dehors de la BMP (et permet de supposer que vous vous souciez de ce genre de justesse).

  • HTML: Comment voulez-vous tourner &#65536; en une chaîne d' wchar_t?

  • Éditeur de texte: Comment trouvez-vous le graphème cluster limites en wchar_t chaîne de caractères?

Si je sais que l'encodage d'une chaîne de caractères, je peux examiner les caractères directement. Si je ne connais pas l'encodage, j'ai l'espoir que tout ce que je veux faire avec une chaîne de caractères est mis en œuvre par une fonction de la bibliothèque quelque part. Si la portabilité des wchar_t est un peu hors de propos que je ne considère pas qu'il est surtout utile type de données.

Les exigences de votre programme peuvent varier d' wchar_t peut bien fonctionner pour vous.

6voto

Luc Danton Points 21421

Étant donné qu' iconv n'est pas "pur standard C/C++", je ne pense pas que vous êtes la satisfaction de vos propres spécifications.

Il y a de nouveaux codecvt facettes venir avec char32_t et char16_t donc je ne vois pas comment on peut se tromper aussi longtemps que vous êtes cohérent et en choisir un type char + encodage si les facettes sont ici.

Les facettes sont décrites dans 22.5 [paramètres régionaux.stdcvt] (à partir de n3242).


Je ne comprends pas comment ce n'est pas satisfaire à au moins certaines de vos exigences:

namespace ns {

typedef char32_t char_t;
using std::u32string;

// 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::wstring_convert<std::codecvt_utf8<char_T>, char_T> converter0;

inline std::string
to_interface0(string const& s)
{
    return converter0().to_bytes(s);
}

inline string
from_interface0(std::string const& s)
{
    return converter0().from_bytes(s);
}

// Communitate with interface1, which wants utf-16

// Doesn't have to be public either
typedef std::wstring_convert<std::codecvt_utf16<char_T>, char_T> converter1;

inline std::wstring
to_interface0(string const& s)
{
    return converter1().to_bytes(s);
}

inline string
from_interface0(std::wstring const& s)
{
    return converter1().from_bytes(s);
}

} // ns

Ensuite, votre code peut utiliser ns::string, ns::char_t, LIT'A' & LIT"Hello, World!" avec insouciance, sans savoir quelle est la représentation sous-jacente. Ensuite, utilisez from_interfaceX(some_string) chaque fois que c'est nécessaire. Il n'a pas d'incidence sur le mondial de paramètres régionaux ou des cours d'eau. Les accompagnateurs peuvent être aussi intelligent que nécessaire, par exemple, codecvt_utf8 peut traiter avec des "en-têtes", ce qui je suppose est Standardese de choses difficiles comme la NOMENCLATURE (idem codecvt_utf16).

En fait, j'ai écrit ci-dessus pour être aussi court que possible, mais vous auriez vraiment envie aides comme ceci:

template<typename... T>
inline ns::string
ns::from_interface0(T&&... t)
{
    return converter0().from_bytes(std::forward<T>(t)...);
}

qui vous donne accès à 3 des surcharges pour chaque [from|to]_bytes des membres, d'accepter les choses comme par exemple, const char* ou des plages.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by: