Résumé
Un std::string stocke des octets, non des lettres, et dans le texte d’aujourd’hui une lettre peut occuper d’un à quatre octets. Cette leçon sépare trois choses que le langage courant confond : le caractère abstrait, qu’Unicode identifie par un nombre appelé point de code ; les octets par lesquels UTF-8 le code ; et la figure que l’écran dessine. Elle explique comment UTF-8 répartit les bits d’un point de code entre ses octets, pourquoi le mot espagnol « año » (année) en occupe quatre et comment on compte les caractères d’une chaîne sans la décoder. Elle montre ensuite quelles opérations cassent le texte (inverser octet par octet, passer en majuscules octet par octet, couper au milieu d’un caractère), lesquelles fonctionnent sans aucune précaution (stocker, copier, concaténer, chercher, écrire) et ce que la bibliothèque standard de C++23 ne fait pas : les majuscules hors d’ASCII, l’ordre alphabétique selon la langue, la normalisation.
Objectifs d’apprentissage
- Expliquer comment UTF-8 code un point de code sur un à quatre octets, et décoder à la main les octets du « ñ ».
- Prédire la taille en octets d’une chaîne contenant des lettres non ASCII, comme
"año"(4) ou"programación"(13). - Compter les caractères d’une chaîne UTF-8 en comptant les octets qui ne sont pas des octets de continuation, avec la condition
(c & 0xC0) != 0x80. - Identifier les opérations qui cassent le texte UTF-8 (inverser octet par octet,
std::toupperpar octet, couper au milieu d’un caractère) et décider que faire dans chaque cas.
Caractères, points de code et octets
Trois niveaux
La leçon Caractères, booléens, conversions et constantes a présenté le char comme un entier d’un octet qui s’écrit comme une lettre selon la table ASCII, et a averti que 'ñ' n’y tient pas ; la leçon Compiler, exécuter et configurer a montré que "año" occupe quatre octets et a préparé la console pour l’écrire. Cette leçon explique pourquoi. ASCII attribue un nombre à 128 caractères, de 0 à 127, qui tiennent sur sept bits : les lettres de l’anglais, les chiffres, les signes de ponctuation et quelques codes de contrôle. Or le texte réel contient des ñ, des accents, des trémas, des lettres grecques, des idéogrammes chinois, des signes mathématiques et des emojis, qui se comptent par centaines de milliers. Il s’ensuit qu’il faut, d’abord, une table beaucoup plus grande, et ensuite, une manière d’écrire ses nombres en octets.
Il convient de distinguer trois niveaux. Le premier est le caractère abstrait : la lettre « ñ minuscule », indépendamment de la manière dont on la stocke ou la dessine. Unicode est la norme qui attribue à chaque caractère abstrait un nombre, son point de code, qui s’écrit U+ suivi d’au moins quatre chiffres hexadécimaux : le « ñ » est U+00F1 (241), le « A » est U+0041 (65, le même nombre qu’en ASCII), le « € » est U+20AC (8364). Les points de code vont de 0 à U+10FFFF, un peu plus d’un million. Le deuxième niveau est le codage : la règle qui convertit chaque point de code en une suite d’octets. UTF-8 est le codage qu’utilisent le web, les systèmes actuels et ce cours. Le troisième niveau est le rendu : la figure que la police de caractères dessine à l’écran, qui peut changer d’une police à l’autre sans que le caractère change.
Si tel est le cas, une donnée textuelle n’est pas un texte tant qu’on ne connaît pas son codage : les octets c3 b1 sont le « ñ » en UTF-8, mais lus avec la table d’une ancienne console ils sont deux symboles sans rapport avec lui, ce qui est la panne que la leçon 1.4 a appris à diagnostiquer. La même question, vue depuis la composition de documents, apparaît dans la leçon « Langues et polices » du cours LaTeX pour la rédaction académique.
Voir les octets
Pour voir les octets d’une chaîne, il suffit de la parcourir : un std::string et un std::string_view sont des suites de char, et la boucle for basée sur un intervalle en livre les octets un par un. Pour les écrire en hexadécimal, on se sert de la spécification de format {:02x} (deux chiffres hexadécimaux, avec un zéro à gauche). Comme avec {:d} dans la leçon 2.4, et par la même correction de la norme adoptée en 2023 et appliquée par GCC 15, {:02x} écrit un char comme un octet non signé. La règle compte, parce que char est signé dans GCC sur x86-64 : l’octet c3, converti en int, est le nombre , que {:02x} écrirait -3d.
#include <print>#include <string_view>// Octets d’un texte.// Précondition : aucune.// Postcondition : écrit une ligne avec text, sa taille en octets et chaque octet en hexadécimal.void printBytes(std::string_view text){ std::print("{:<4}{}:", text, text.size()); for (const char c : text) { std::print(" {:02x}", c); } std::println("");}int main(){ printBytes("A"); printBytes("ñ"); printBytes("€"); printBytes("😀"); printBytes("año");}A 1: 41ñ 2: c3 b1€ 3: e2 82 ac😀 4: f0 9f 98 80año 4: 61 c3 b1 6fChaque ligne montre le texte, sa taille et ses octets. Un caractère de chaque longueur : le « A » occupe un octet, le « ñ » deux, le « € » trois et l’emoji quatre. Le mot « año » occupe quatre octets, 61, c3 b1 et 6f, et son size() vaut 4, non 3. On remarquera que le « A » est l’octet 41, le même qu’en ASCII : UTF-8 écrit les 128 caractères d’ASCII exactement comme ASCII.
La figure suit le codage du « ñ » de haut en bas : le point de code, ses bits, la répartition de ces bits et les deux octets obtenus, avec les bits de préfixe mis en évidence.
Démonstration
Le « ñ » est le point de code
U+00F1, c’est-à-dire .En binaire, s’écrit
11110001. Il est supérieur à 127 et inférieur à 2048, de sorte qu’UTF-8 l’écrit sur deux octets, qui transportent onze bits : on complète par des zéros à gauche jusqu’à onze,00011110001.Les onze bits se répartissent en deux groupes : les cinq premiers,
00011, et les six derniers,110001.Le premier octet porte le préfixe
110, qui annonce un caractère de deux octets, suivi des cinq bits du premier groupe :11000011. Le second porte le préfixe10, qui marque un octet de continuation, suivi des six bits restants :10110001.En hexadécimal,
1100 0011estc3et1011 0001estb1: les deux octets que le programme précédent a écrits pour le « ñ ». Le changement de base entre binaire et hexadécimal, de quatre bits en quatre bits, est celui de la leçon « Systèmes de numération de position » d’Algèbre et géométrie I.
Chargement du contenu…