web-dev-qa-db-fra.com

Pourquoi n'y a-t-il pas de modificateur de finalité en C++ comme pour la signature?

(Je suppose que cette question pourrait s'appliquer à de nombreux langages typés, mais j'ai choisi d'utiliser le C++ à titre d'exemple.)

Pourquoi n'y a-t-il pas moyen de simplement écrire:

struct foo {
    little int x;   // little-endian
    big long int y; // big-endian
    short z;        // native endianness
};

spécifier la finalité pour des membres, des variables et des paramètres spécifiques?

Comparaison à la signature

Je comprends que le type d'une variable détermine non seulement le nombre d'octets utilisés pour stocker une valeur, mais également la manière dont ces octets sont interprétés lors de l'exécution de calculs.

Par exemple, ces deux déclarations allouent chacune un octet et, pour les deux octets, chaque séquence de 8 bits possible est une valeur valide:

signed char s;
unsigned char u;

mais la même séquence binaire peut être interprétée différemment, par ex. 11111111 signifierait -1 quand assigné à s mais 255 quand assigné à u. Lorsque des variables signées et non signées sont impliquées dans le même calcul, le compilateur s’occupe (principalement) des conversions appropriées.

À mon sens, l’endianisme n’est qu’une variante du même principe: une interprétation différente d’un modèle binaire basée sur des informations au moment de la compilation concernant la mémoire dans laquelle il sera stocké.

Il semble évident d’avoir cette fonctionnalité dans un langage typé permettant la programmation de bas niveau. Cependant, cela ne fait pas partie de C, C++ ou de tout autre langage que je connais et je n'ai trouvé aucune discussion à ce sujet en ligne.

Mettre à jour

Je vais essayer de résumer quelques-uns des nombreux commentaires que j’ai reçus la première heure après avoir demandé:

  1. la signature est strictement binaire (signée ou non signée) et le sera toujours, contrairement à endianness, qui a également deux variantes connues (grande et petite), mais également des variantes moins connues telles que le mélange endian/middle endian. De nouvelles variantes pourraient être inventées à l'avenir.
  2. l'endianisme est important pour accéder à des valeurs d'octets multiples sur plusieurs octets. Au-delà de la simple endianité, de nombreux aspects affectent la structure de la mémoire des structures multi-octets. Ce type d'accès est donc en grande partie déconseillé.
  3. C++ vise à cibler une machine abstraite et à minimiser le nombre d'hypothèses relatives à la mise en œuvre. Cette machine abstraite n'a aucune endianité.

De plus, je réalise maintenant que la signature et l’endianisme ne sont pas une analogie parfaite, car:

  • endianness définit uniquement comment quelque chose est représenté comme une séquence binaire, mais maintenant ce qui peut être représenté. big int et little int auraient exactement la même plage de valeurs.
  • la signature définit comment les bits et les valeurs réelles sont mis en correspondance, mais affecte également ce qui peut être représenté, par ex. -3 ne peut pas être représenté par un unsigned char et (en supposant que char a 8 bits) 130 ne peut pas être représenté par un signed char.

Ainsi, le fait de modifier l’endianisme de certaines variables ne changera jamais le comportement du programme (sauf pour l’accès octet par octet), contrairement à un changement de signature.

57
Lena Schimmel

Que dit la norme

[intro.abstract]/1 :

Les descriptions sémantiques de ce document définissent une machine abstraite non déterministe paramétrée . Ce document n'impose aucune exigence à la structure des implémentations conformes . En particulier, ils n'ont pas besoin de copier ou d'imiter la structure de la machine abstraite . Plutôt, des implémentations conformes sont requises pour émuler (uniquement) le comportement observable de la machine abstraite, comme expliqué ci-dessous.

C++ n’a pas pu définir de qualificatif d’endianisme car il n’a pas de concept d’endianisme.

Discussion

À propos de la différence entre la signification et l’endianisme, écrit OP

À mon sens, l’endianisme n’est qu’une variante du même principe [(signature)]: une interprétation différente d’un modèle binaire basée sur des informations de compilation relatives à la mémoire dans laquelle il sera stocké.

Je dirais que la significativité a un aspect sémantique et un aspect représentatif1. Ce que [intro.abstract]/1 implique, c’est que le C++ ne s’occupe que de sémantique, et ne traite jamais de la façon dont un nombre signé devrait être représenté en mémoire.2. En fait, "bit de signe" n'apparaît qu'une seule fois dans les spécifications C++ et fait référence à une valeur définie par l'implémentation.
.__ Par contre, l’endianité n’a qu’un aspect représentatif: l’endianité n’a aucun sens.

Avec C++ 20, std::endian apparaît. Il est toujours défini par l’implémentation, mais testons l’endian de l’hôte sans dépendre de d’anciennes astuces basées sur un comportement indéfini .


1) Aspect sémantique: un entier signé peut représenter des valeurs inférieures à zéro; aspect représentatif: il faut par exemple réserver un peu pour transmettre le signe positif/négatif.
2) Dans le même ordre d'idées, C++ ne décrit jamais comment un nombre à virgule flottante devrait être représenté. IEEE-754 est souvent utilisé, mais il s'agit d'un choix fait par la mise en œuvre, dans tous les cas imposé par la norme: [basic.fundamental]/8" La représentation des valeurs des types à virgule flottante est définie par l'implémentation ".

52
YSC

Les entiers (en tant que concept mathématique) ont le concept de nombres positifs et négatifs. Ce concept abstrait de signe comporte différentes implémentations matérielles.

L'endianisme n'est pas un concept mathématique. Little-endian est une astuce d’implémentation matérielle destinée à améliorer les performances de l’arithmétique des nombres entiers multiples sur deux octets sur un microprocesseur avec des registres de 16 ou 32 bits et un bus de mémoire de 8 bits. Sa création nécessitait l'utilisation du terme big-endian pour décrire tout le reste ayant le même ordre d'octet dans les registres et en mémoire.

La machine abstraite en C comprend le concept d’entiers signés et non signés, sans détails, sans nécessiter d’arithmétique à deux complémentets, d’octets sur 8 bits ni de méthode pour stocker un nombre binaire en mémoire.

PS: Je conviens que la compatibilité des données binaires sur le net ou en mémoire/stockage est une PIA.

3
Chad Farmer

C'est une bonne question et j'ai souvent pensé qu'une telle chose serait utile. Cependant, vous devez garder à l'esprit que les objectifs C en matière d'indépendance de la plate-forme et de finalité ne sont importants que lorsqu'une telle structure est convertie en une structure de mémoire sous-jacente. Cette conversion peut se produire lorsque vous convertissez un tampon uint8_t en int, par exemple. Tandis qu’un modificateur d’endianness semble ordonné, le programmeur doit toujours prendre en compte d’autres différences de plate-forme, telles que les tailles int, l’alignement et la compression des structures. Il est préférable de coder les fonctions de conversion explicites, puis de laisser l'optimiseur du compilateur générer le code le plus efficace pour chaque plate-forme prise en charge.

2
D Dowling

L'endianisme ne fait pas partie intégrante d'un type de données, mais plutôt de sa structure de stockage.

En tant que tel, cela ne ressemblerait pas vraiment à signé/non signé, mais plutôt à la largeur d'un champ de bits dans la structure. Semblables à ceux-ci, ils pourraient être utilisés pour définir des API binaires.

Donc, vous auriez quelque chose comme

int ip : big 32;

qui définirait à la fois la structure de stockage et la taille des nombres, laissant au compilateur le soin de faire en sorte que l'utilisation du champ soit la meilleure possible. Ce n'est pas évident pour moi ce que les déclarations autorisées devraient être.

2
user9026597

Réponse courte: s'il ne devait pas être possible d'utiliser des objets dans des expressions arithmétiques (sans opérateurs surchargés) impliquant des ints, ces objets ne devraient pas être de type entier. Et il n'y a aucun intérêt à autoriser l'addition et la multiplication d'ints big-endian et little-endian dans la même expression.

Réponse plus longue:

Comme quelqu'un l'a mentionné, l'endianité est spécifique au processeur. Ce qui signifie vraiment que c'est ainsi que sont représentés les nombres lorsqu'ils sont utilisés comme nombres dans le langage machine (comme adresses et comme opérandes/résultats d'opérations arithmétiques).

La même chose est "en quelque sorte" vraie de la signalisation. Mais pas au même degré. La conversion de la signalisation sémantique en langue en signalisation acceptée par le processeur est une chose à faire pour utiliser des nombres en tant que nombres. La conversion de big-endian en little-endian et inversé est une chose à faire pour utiliser des nombres en tant que données (envoyez-les sur le réseau ou représentez des métadonnées sur des données envoyées sur le réseau, telles que la longueur de la charge utile). 

Cela dit, cette décision semble être principalement motivée par des cas d'utilisation. Le revers de la médaille est qu’il existe une bonne raison pragmatique d’ignorer certains cas d’utilisation. Le pragmatisme découle du fait que la conversion de l’endianité est plus coûteuse que la plupart des opérations arithmétiques. 

Si une langue avait la sémantique voulant que les nombres soient peu petits, elle permettrait aux développeurs de se tirer une balle dans le pied en forçant la finesse des nombres dans un programme très arithmétique. Si développé sur une machine little-endian, cette application de endianness serait un no-op. Mais lorsque porté sur une machine big-endian, il y aurait beaucoup de ralentissements inattendus. Et si les variables en question étaient utilisées à la fois pour l'arithmétique et pour les données de réseau, le code deviendrait complètement non portable. 

Ne pas avoir ces sémantiques endian ou les forcer à être explicitement spécifiques au compilateur oblige les développeurs à passer par l'étape mentale consistant à penser que les nombres sont "lus" ou "écrits" dans/à partir du format réseau. Cela rendrait le code qui effectue des va-et-vient entre ordre de byte réseau et hôte, au milieu d'opérations arithmétiques, encombrant et moins susceptible d'être la méthode préférée d'écriture par un développeur paresseux. 

Et puisque le développement est une entreprise humaine, rendre les mauvais choix inconfortables est une bonne chose.

Edit : voici un exemple montrant comment cela peut mal se passer: Supposons que les types little_endian_int32 et big_endian_int32 sont introduits. Alors little_endian_int32(7) % big_endian_int32(5) est une expression constante. Quel est son résultat? Les nombres sont-ils implicitement convertis au format natif? Si non, quel est le type de résultat? Pire encore, quelle est la valeur du résultat (qui dans ce cas devrait probablement être identique sur toutes les machines)? 

Encore une fois, si les nombres multi-octets sont utilisés en tant que données simples, les tableaux de caractères sont tout aussi bons. Même s’il s’agit de «ports» (qui sont en réalité des valeurs de recherche dans des tables ou leurs hachages), ce ne sont que des séquences d’octets plutôt que des types entiers (sur lesquels on peut faire de l’arithmétique). 

Maintenant, si vous limitez les opérations arithmétiques autorisées sur les nombres explicitement-endian aux seules opérations autorisées pour les types de pointeur, vous aurez peut-être un meilleur argument en matière de prévisibilité. Alors, myPort + 5 prend tout son sens même si myPort est déclaré comme quelque chose comme little_endian_int16 sur une machine big endian. Idem pour lastPortInRange - firstPortInRange + 1. Si l'arithmétique fonctionne comme pour les types de pointeur, cela ferait ce que vous attendiez, mais firstPort * 10000 serait illégal. 

Ensuite, bien sûr, vous discuterez de la question de savoir si la fonctionnalité bloat est justifiée par un bénéfice éventuel.

2
Dmitry Rubanovich

Du point de vue du programmeur pragmatique recherchant Stack Overflow, il est intéressant de noter que l’esprit de cette question peut être résolu avec une bibliothèque d’utilitaires. Boost a une telle bibliothèque:

http://www.boost.org/doc/libs/1_65_1/libs/endian/doc/index.html

La fonctionnalité de la bibliothèque qui ressemble le plus à la fonctionnalité de langage en discussion est un ensemble de types arithmétiques tels que big_int16_t.

1
Peter

Parce que personne n'a proposé de l'ajouter à la norme, et/ou que le développeur du compilateur n'en a jamais ressenti le besoin.

Vous pourriez peut-être le proposer au comité. Je ne pense pas qu'il soit difficile de l'implémenter dans un compilateur: les compilateurs proposent déjà des types fondamentaux qui ne sont pas des types fondamentaux pour la machine cible.

Le développement de C++ est une affaire de tous les codeurs C++.

@ Schimmel. N'écoutez pas les gens qui justifient le statu quo! Tous les arguments cités pour justifier cette absence sont plus que fragiles. Un étudiant en logicien pourrait trouver leur contradiction sans rien connaître de l'informatique. Proposez-le simplement et ne vous inquiétez pas des conservateurs pathologiques (Conseillez: proposez de nouveaux types plutôt qu'un qualificateur car les mots-clés unsigned et signed sont considérés comme des erreurs).

0
Oliv