Proposition C++ : les octets doivent faire exactement 8 bits
(open-std.org)- P3477R0 est une proposition de modification du standard visant à ne plus laisser la taille d’un octet en C++ à la valeur dépendante de l’implémentation
CHAR_BIT, mais à la fixer à exactement 8 bits - Les plateformes modernes convergent déjà en pratique vers des octets de 8 bits, et GCC, LLVM et MSVC utilisent aussi des valeurs par défaut ou des macros liées fixées à 8
- POSIX exige
CHAR_BIT == 8depuis POSIX.1-2001, et l’adoption de la représentation des entiers en complément à deux dans C++20 et C23 va dans le même sens - La prise en charge des octets non 8 bits laisse de petites exceptions dans l’ensemble du langage, des bibliothèques et de la toolchain, et crée selon l’auteur une charge liée à des cas limites peu cohérents avec l’usage réel du C++ moderne
- Des architectures atypiques comme le PDP-10 ou certains DSP existent, mais la question centrale est de savoir si les nouveaux standards C++ doivent continuer à conserver cette complexité pour elles
Objectif des changements de P3477R0
- C++ reprend la macro
CHAR_BITdu C, et cette valeur est actuellement une valeur dépendante de l’implémentation indiquant le nombre de bits dans un octet - P3477R0 propose de modifier le standard C++ pour exiger officiellement que l’octet fasse 8 bits
- À l’époque des débuts de l’informatique, la souplesse permettant différentes tailles d’octet avait du sens, mais l’idée de fond est qu’aujourd’hui presque tout le matériel moderne converge vers l’octet de 8 bits
État actuel des compilateurs et des plateformes
- Les principaux compilateurs traitent déjà l’octet de 8 bits comme la réalité par défaut
- Parmi les anciens cas pris en charge par GCC,
dsp16xxa été supprimé en 2004 et1750aen 2002 - Des recherches sur le Web montrent quelques ports GCC externes où
BITS_PER_UNITn’est pas égal à 8, mais ils ne semblent pas pertinents pour le C++ moderne
Tendance côté POSIX et représentation des entiers
- POSIX impose depuis POSIX.1-2001 les conditions suivantes
- un octet de 8 bits exactement
CHAR_BITà8SCHAR_MAXà127,SCHAR_MINà-128,UCHAR_MAXà255
- POSIX explique qu’à la suite de l’ajout de
int8_t, il faut uncharsur 8 bits et une arithmétique en complément à deux - Depuis P0907r4, C++20 ne prend en charge que le stockage en complément à deux, et C23 suit la même direction
- Parmi les systèmes d’exploitation actuellement conformes à POSIX, sont cités AIX, HP-UX, INTEGRITY, macOS, OpenServer, UnixWare, VxWorks et vz/OS
Le coût laissé par les octets non 8 bits
- Les logiciels conçus pour des octets de 8 bits et ceux destinés à des octets non 8 bits ne sont pas compatibles entre eux, et le code C/C++ pour octets non 8 bits ressemble en pratique à des dialectes incompatibles du C et du C++
- La prise en charge d’architectures à octets non 8 bits laisse une petite complexité inutile dans plusieurs parties du langage et des bibliothèques
- Les compilateurs et les toolchains doivent continuer à gérer des cas limites qui ne reflètent pas les usages modernes
- Les nouveaux programmeurs peuvent facilement être déroutés par cette caractéristique exotique du C++
- Certains programmeurs expérimentés finissent, selon l’auteur, par perdre du temps sur une “portabilité” visant des plateformes inexistantes
Architectures d’exception et compromis possibles
- La proposition reconnaît elle aussi que des processeurs à octets non 8 bits existent encore
- La question clé est de savoir si ces processeurs sont pertinents pour le C++ moderne, et si leurs utilisateurs adopteront réellement les nouvelles versions du C++
- Un compromis consistant à exiger
CHAR_BIT % 8 == 0est également évoqué, mais il n’a de sens que si le comité décide de continuer à prendre en charge des DSP ou autres processeurs oùCHAR_BITn’est pas 8 mais un multiple de 8 - Le PDP-10 fait partie de la discussion, mais il est précisé que le PDP-11 utilise des octets de 8 bits
- Certains DSP traitent des mots de 24 ou 32 bits comme des “octets”, ce qui était défendable à une époque où la taille des mots variait fortement et où la notion d’octet n’était pas standardisée
Orientation des changements de formulation dans le standard
- L’idée est de modifier la définition de l’octet dans
intro.memoryafin d’indiquer explicitement que l’octet, unité de stockage de base du modèle mémoire du C++, fait 8 bits - Dans
climits, une modification de formulation est proposée pour fixerCHAR_BITà8 - Dans
cstdint, puisque l’octet ferait 8 bits, les types entiers à largeur fixée commeint8_t,uint8_tet les macros associées ne seraient plus optionnels - Parmi les types utilisant
_N_, ceux dontNn’est ni8, ni16, ni32, ni64resteraient optionnels - La modification inclut aussi la suppression de quatre clauses mandates liées à
CHAR_BIT == 8danslocalization
Relation avec le standard C
- La proposition examine si C++ doit continuer à se préoccuper des architectures à octets non 8 bits
- Le comité C peut aboutir à une conclusion différente pour le langage C
- Il serait idéal que les deux comités s’alignent, mais cette proposition laisse au groupe de liaison WG14 et SG22 le soin d’informer WG21
1 commentaires
Avis sur Hacker News
Dans la série de JF « Ne pourrait-on pas reconnaître que les vrais ordinateurs fonctionnent tous comme ça ? », il y avait déjà un épisode sur le fait que les entiers signés sont en complément à deux : "Signed Integers are Two’s Complement"
Sauf que cela pourrait rendre obsolète ce classique du code du noyau Linux : https://github.com/torvalds/linux/blob/master/include/math-emu/double.h#L29
En 1986, quand j’étais stagiaire, j’ai écrit du code C sur un BBN C/70 qui utilisait des octets de 10 bits ; c’était une expérience atroce, et le simple fait qu’une telle machine ait existé relevait d’un accident cosmique, au sens négatif
Il utilisait des mots de 36 bits et des octets de 7 bits, et quand on remplissait un mot avec des octets, il restait des bits. Comme on m’avait chargé de lire des bandes contenant des données binaires au format 8 bits, c’était le chaos
Mais du C en 10 bits, c’est une autre histoire
D a fait un grand pas en avant en décidant que : un octet fait 8 bits, short fait 16 bits, int 32 bits, long 64 bits, l’arithmétique est en complément à deux, et les flottants sont des flottants IEEE
Cela a évité un temps énorme passé à essayer d’abstraire ces choses pour finir par se tromper, et des millions de personnes ont poussé un soupir de soulagement. Le jeu de caractères était aussi Unicode, pas EBCDIC ni RADIX-50
u8/i8,u16/i16,u32/i32,u64/i64, et l’arithmétique se choisit aussi explicitementLe débordement de
+est un comportement incorrect, donc il interrompt l’exécution en debug et en releasesafe ;+%est un wrapping en complément à deux,+|est une arithmétique saturée.@addWithOverflow()renvoie un tuple du type d’origine et deu1, etstd.math.add()renvoie une erreur en cas de débordement.f16,f32,f64,f80,f128sont aussi des types flottants IEEE de la longueur en bits correspondante. La longueur d’un octet n’a pas d’importance : sur une machine à octets de 12 bits, il suffit d’utiliseru12eti12u8,i32sont largement meilleurs à tous points de vueunsigned, mais il a correctement standardisé le nombre de bits des types primitifsbyte = 8 bits,short = 16,int = 32,long = 64,float = 32 bit IEEE,double = 64 bit IEEEIl y a encore des gens qui doivent gérer des DSP : https://thephd.dev/conformance-should-mean-something-fputc-and-freestanding#we-cannot-program-on--vibes-
Personnellement, je documente pour m’amuser une console fantasy 12 bits non implémentée, avec « 50 % de bits en plus par octet que la concurrence ! », et j’y ai aussi mis des inventions comme « UTF-12 »
Cela fait des années que je pose la question sans obtenir de réponse positive ; le seul exemple mentionné était à peu près TI, donc j’ai ajouté l’information dans le brouillon mis à jour : https://isocpp.org/files/papers/D3477R1.html
CHAR_BIT=32dans une version comme C++30, je ne hurlerais pas au scandaleJe me demande si C++ peut vraiment supprimer ou simplifier quelque chose
C’est une vraie question, et je n’ai pas suivi le sujet en détail. J’ai entendu dire que
rand()était cassé et impossible à corriger, mais aux dernières nouvelles il n’était même pas encore prévu pour dépréciation. Cette proposition ressemble à un test du type : « peut-on aussi supprimer la prise en charge de solutions à des problèmes que littéralement personne ne rencontre ? »Le C et le C++ modernes ont abandonné cela et exigent le complément à deux. Ici, la distinction « as if » n’a pas vraiment d’importance en pratique, et on peut l’appliquer de la même manière à
CHAR_BIT, donc il existe clairement un précédent pour ce genre de changementranda aussi été rendu déprécié, avec des alternatives fourniesIl y a aussi
p2809 Trivial infinite loops are not Undefined Behavior,p1152 Deprecating volatile,p0907 Signed Integers are Two's Complement,p2723 Zero-initialize objects of automatic storage duration,p2186 Removing Garbage Collection Support. Donc oui, changer les choses est possibleLes spécifications d’exceptions ont aussi été supprimées, même si certains voudraient les ressusciter pour les exceptions de type valeur.
auto_ptra également été supprimé à cause de sa conception cassée. Cela dit, côté simplification, ce n’est pas vraiment mieux, puisqu’il faut toujours connaître l’ancienne façon de faireDu genre : il faudrait un nouveau symbole C++ qui désigne de façon fiable un octet de 8 bits sans casser la compatibilité. Par exemple
unsigned byte8,signed byte8en complément à deux, et mêmebyte8avec un comportement de signe non défini. On pourrait aussi créerunsigned decimal byte8etsigned decimal byte8, limités aux plages 0~10 et -10~+10 pour les comptables,centimal byte8de 0~100 et -100~+100 pour les comptables qui comptent même le coût des octets, un type vaguement suffisant pour le champaged’une base de données, et bien sûr ajouter aussifloat byte8rand()est cassé. Il produit des valeurs qui ont l’air aléatoires, et c’est son butÉvidemment qu’il ne produit pas de nombres aléatoires cryptographiquement sûrs, et les fonctions équivalentes dans d’autres langages non plus. Pour un entier assez aléatoire et rapide à calculer,
rand()fonctionne suffisamment bienMerci de vous intéresser à cette proposition ; j’ai préparé un brouillon mis à jour à partir des retours reçus jusqu’ici : https://isocpp.org/files/papers/D3477R1.html
La phrase suivante m’a particulièrement marqué : « Le problème n’est pas de savoir s’il existe encore des architectures où un byte ne fait pas 8 bits. Il y en a ! Le problème est de savoir si elles se soucient du C++ moderne, et si le C++ moderne se soucie d’elles »
J’ai des sentiments partagés sur cette proposition. D’un côté, elle est manifestement correcte, et il n’existe pas d’usage significatif où
CHAR_BITne vaut pas 8De l’autre, j’ai aussi l’impression qu’on cède à une vision du monde juste selon laquelle le monde devrait avoir du sens et être raisonnable à partir d’un modèle personnel et excessivement simplifié de l’intérieur des ordinateurs. Cette approche vous emmène assez loin, mais finit dans une impasse ; au bout du compte, il faut admettre qu’on ne sait rien et que le mieux qu’on puisse faire est un raisonnement formel disant qu’on a construit un programme correct à condition que la documentation soit correcte. C’est un grand saut intellectuel, et personnellement, plus on tarde à être forcé de l’admettre, plus il est difficile de le franchir ensuite. Cela dit, les projets d’électronique physique semblent redevenir populaires chez les débutants ces temps-ci, donc j’espère que « lis la documentation » sera remplacé comme nouveau standard par « lis cette foutue datasheet »
autoconf, je le vois vérifier le nombre de bits dans un byte et l’enregistrer dansconfig.h. Comme si quelqu’un avait vraiment prévu d’agir en fonction de cette valeurIl faudrait un moyen d’examiner tout l’ancien code pendant la compilation pour vérifier si cette macro est déjà utilisée. Ce type de changement cassant risque aussi de fracturer le langage. Il n’est pas évident non plus de tester si une base de code existante utilise la macro
CHAR_BIT, ni si elle peut être mise à jour vers un nouveau compilateur. On peut aussi se demander quelles bibliothèques seraient considérées comme cassées, ou si des problèmes apparaîtraient lors de l’interaction avec d’autres codes compilés en utilisantCHAR_BIT. Je suis d’accord sur le fait que c’est contre-intuitif, mais il vaudrait mieux commencer par créer des outils de conversion, démontrer que c’est sûr même dans les cas extrêmes, puis seulement effectuer la transitionJ’aime bien cette proposition : elle est à la fois incontestable et incroyablement épicée
Imposer
int8_t == char == 8 bits, ça me va tout à fait, mais je ne suis pas sûr qu’il faille propager l’idée fausse selon laquelle un byte fait 8 bitsUn byte de 8 bits s’appelle un octet. En même temps, depuis C++17,
byteest déjà en quelque sorte un « alias » dechar: https://en.cppreference.com/w/cpp/types/byteEn 45 ans, je n’ai jamais vu « byte » utilisé avec un autre sens ; donc s’il existe une définition de « byte » qui ne fait pas 8 bits, il faudrait une source
Ce n’est pas un énoncé descriptif, c’est un énoncé normatif
int8 == signed charstd::cout << (int8_t)32 << std::endl;devrait évidemment afficher 32Cela n’a rien à voir avec C++, mais j’aime assez l’idée d’un micro-ordinateur rétro à octets de 6 bits. Le mot ferait alors 24 bits.
Les micro-ordinateurs manipulent généralement un petit nombre d’objets et privilégient les tableaux plutôt que les pointeurs, ce qui permettrait d’économiser de la mémoire. Le VGA utilisait 6 bits par couleur, on peut créer un alphabet lisible avec une matrice de bits 6x4, les langages LISP de base ou Forth peuvent aussi tenir dans un alphabet 6 bits, et le System/360 d’origine n’utilisait que des adresses sur 24 bits. Une mémoire de 12 Mio composée d’unités de 6 bits adressables indépendamment devrait suffire à tout le monde. Si ce n’est pas assez, il suffit d’étendre naturellement FAT-12 en FAT-24, ou d’utiliser des pointeurs 48 bits, aussi utiles que des pointeurs 64 bits.