URL: https://linuxfr.org/users/trim/journaux/vous-avez-dit-caractere Title: Vous avez dit "caractère" ? Authors: Adrien Dorsaz Date: 2022年09月04日T23:16:12+02:00 License: CC By-SA Tags: unicode, caractère, utf8, c++, langage_c et c Score: 21 Ces derniers jours, j'ai cherché à mieux comprendre comment gérer UTF-8 dans une de mes applications hobby et j'ai appris pas mal de choses :) D'abord, j'avais oublié que ASCII était codé sur 7 bits et non pas 8 bits. C'est grâce à ça que UTF-8 est automatiquement compatible avec ASCII (UTF-8 est codé avec des blocs de 8-bits, il leur a suffit de dire que le premier bit est 0 pour les 127 premiers Unicodes encodé en UTF-8). Les 7 bits m'ont surpris, car ce n'est pas dans nos habituelles puissances de 2. Mais en fait, ASCII [fait partie](https://www.quora.com/Why-was-the-ASCII-code-originally-7-bits-long) d'une époque où la mémoire et les disques étaient vraiment très cher et il valait donc vraiment mieux ne pas être trop gourmand. J'ai également appris que pour le langage C, le type `char` n'est pas forcément dédié aux caractères, mais plutôt à un [stockage d'au moins 8 bits](https://stackoverflow.com/questions/75191/what-is-an-unsigned-char)[^1]. En pratique, les [tailles minimum](https://en.wikipedia.org/wiki/C_data_types) pour les types C, sont: `char` avec au moins 8 bits (1 octet), `short` et `integer` 16 bits, `long` 32 bits et `long long` 64 bits. Donc, pour pouvoir stocker des entiers sur (au moins) 8 bits (et non pas sur au moins 16 bits), il faut utiliser `char`. C'est pour stocker ces entiers que l'on a aussi `signed char` et `unsigned char`, même si ça n'a pas de sens d'avoir un "caractère signé" a priori :) Ensuite, j'ai enfin trouvé à quoi sert GString dans GLib et pourquoi c'est toujours dit "compatible UTF-8" partout dans la documentation des fonctions liées à GString: d'après [sa description](https://developer-old.gnome.org/glib/stable/glib-Strings.html#glib-Strings.description)[^2], il faut juste interpréter une GString comme un tableau dynamique de bytes avec la sûreté d'avoir le caractère `NUL` de terminaison de string et d'avoir une propriété `len` qui donne le nombre d'octet jusqu'à ce caractère `NUL`. En plus ce type est associés à plusieurs fonctions généralistes de gestion de texte. Et là, je me suis dit, mais en fait ça ressemble énormément à `std::string` de C++: un tableau dynamique d'octets avec une propriété `len`. Mais si je me souviens bien, il y a d'autres types de string en C++ pour la gestion Unicode ? À quoi servent-ils ? Eh bien, il y a effectivement [`std::wstring` et 3 autres](https://en.cppreference.com/w/cpp/string/basic_string). `wstring` utilise le type `wchar_t` qui est un "wide character", mais qui n'est de nouveau pas définit explicitement dans le standard C. En cherchant (encore !) des [explications sur StackOverflow](https://stackoverflow.com/questions/13509733/what-is-the-use-of-wchar-t-in-general-programming) à propos de `wchar`, j'ai trouvé ce lien des "personnes qui sont contre leur utilisation": https://utf8everywhere.org/ Ils donnent beaucoup d'information à propos d'UTF-8 vs UTF-16 vs UTF-32 et pourquoi ils pensent que c'était inutile d'inventer `wchar_t` que finalement seul `std::string` était utile au C++[^3]. En reprenant son pendant `GString` en C, j'ai enfin eu la confirmation du pourquoi c'est effectivement suffisant pour stocker des strings en UTF-8, puisqu'il faut juste pouvoir avoir un ByteArray pour le stockage. Pour l’interprétation de la donnée, il faut évidemment utiliser les bonnes fonctions (comme utiliser g_uf8_normalize avant de faire des comparaisons de string, par exemple) et bien comprendre quelle définition de "caractère" on a en tête (utf8everywhere donne 7 définitions différentes et incompatibles !). Voilà, maintenant pour mon application en C, je sais que j'ai besoin de pouvoir traverser un mot donné par itération sur ses "grapheme cluster". Il ne me reste plus qu'à trouver un bon moyen de le faire :) [^1]: le standard [n'est pas explicite](https://web.archive.org/web/20181230041359if_/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf) sur la taille des types de base. `char` doit pouvoir contenir le [basic execution character set](https://en.cppreference.com/w/cpp/language/charset) et garantir que sa valeur numérique est non-négative. Ce qui revient en pratique à utiliser au moins 8 bits pour `char`. [^2]: oui, j'ai donné le lien de l'ancienne documentation, parce que la [nouvelle](https://docs.gtk.org/glib/struct.String.html#description) a perdu cette description. J'ai essayé de [rapporter le bug](https://gitlab.gnome.org/GNOME/gi-docgen/-/issues/139), en espérant l'avoir ouvert dans le bon projet ! [^3]: à la condition que le standard dise que le `basic execution character set` doit être capable de stocker n'importe quelle donnée Unicode. Ce serait facile de le faire grâce à UTF-8 qui est compatible avec la définition actuelle de `char`.