web-dev-qa-db-fra.com

Quelle est la raison des limitations en arithmétique ou en comparaison de pointeur?

En C/C++, l'addition ou la soustraction sur le pointeur n'est défini que si le pointeur résultant se trouve dans l'original pointé objet complet . De plus, la comparaison de deux pointeurs ne peut être effectuée que si les deux objets pointés sont des sous-objets d'un unique objet complet.

Quelles sont les raisons de ces limitations?

J'ai supposé que le modèle de mémoire segmenté (voir ici §1.2.1) pourrait être l'une des raisons, mais comme les compilateurs peuvent définir un ordre total sur tous les pointeurs, comme le montre cette answer , je doute de cela. .

24
Oliv

Il existe des architectures dans lesquelles les espaces programme et données sont séparés. Il est tout simplement impossible soustraire deux pointeurs arbitraires. Un pointeur sur une fonction ou sur des données statiques constera dans un espace d'adressage complètement différent de celui d'une variable normale.

Même si vous avez arbitrairement fourni un classement entre différents espaces d'adressage, il est possible que le type diff_t ait besoin d'une taille plus grande. Et le processus de comparaison ou de soustraction de deux pointeurs serait extrêmement compliqué. C'est une mauvaise idée dans un langage conçu pour la vitesse.

8
Mark Ransom

Vous ne faites que prouver que la restriction pouvait être supprimée - mais, à défaut, cela entraînerait un coût (en termes de mémoire et de code), ce qui était contraire aux objectifs de C.

Plus précisément, la différence doit avoir un type, ptrdiff_t, et on peut supposer qu'il est similaire à size_t.

Dans un modèle de mémoire segmenté, vous avez (normalement) indirectement (normalement) une limite sur la taille des objets - en supposant que les réponses dans: Quelle est la taille réelle de `size_t`,` uintptr_t`, `intptr_t` et` ptrdiff_t` de type 16 systèmes à bits utilisant le mode d'adressage segmenté? sont corrects.

Ainsi, au moins pour les différences, supprimer cette restriction ajouterait non seulement des instructions supplémentaires pour assurer un ordre total - pour un cas de coin sans importance (comme dans une autre réponse), mais dépenserait également le double de la quantité de mémoire utilisée pour les différences, etc.

C a été conçu pour être plus minimaliste et ne pas forcer le compilateur à utiliser de la mémoire et du code pour de tels cas. (À cette époque, les limitations de mémoire importaient davantage.)

Évidemment, il y a aussi d'autres avantages, comme la possibilité de détecter les erreurs lors du mélange de pointeurs provenant de différents tableaux. De même, le mélange d'itérateurs pour deux conteneurs différents n'est pas défini en C++ (à quelques exceptions mineures près) - et certaines implémentations de débogage détectent de telles erreurs.

10
Hans Olsson

La raison est de garder la possibilité de générer un code raisonnable. Ceci s'applique aux systèmes avec un modèle de mémoire à plat ainsi qu'aux systèmes avec des modèles de mémoire plus complexes. Si vous interdisez les angles (peu utiles) comme l'ajout ou la soustraction de tableaux et l'exigence d'un ordre total sur les pointeurs entre les objets, vous pouvez éviter beaucoup de temps système dans le code généré.

Les limitations imposées par la norme permettent au compilateur de formuler des hypothèses sur l'arithmétique de pointeur et de les utiliser pour améliorer la qualité du code. Il couvre à la fois les choses informatiques statiques dans le compilateur plutôt que lors de l'exécution et le choix des instructions et des modes d'adressage à utiliser. A titre d'exemple, considérons un programme avec deux pointeurs p1 et p2. Si le compilateur peut déduire qu'il pointe vers différents objets de données, il peut en toute sécurité supposer que toute opération non basée sur le suivant p1 affectera jamais l'objet pointé par p2. Cela permet au compilateur de réorganiser les charges et les magasins en fonction de p1 sans tenir compte des charges et des magasins en fonction de p2 et vice-versa.

9
Johan

La raison en est que certaines architectures ont une mémoire segmentée et que les pointeurs sur différents objets peuvent pointer sur différents segments de mémoire. La différence entre les deux pointeurs ne serait alors pas nécessairement significative.

Cela remonte au pré-standard C. Le raisonnement C ne le mentionne pas explicitement, mais il donne à penser que c'est la raison, si nous regardons là où il explique pourquoi l'utilisation d'un index de tableau négatif est un comportement non défini justification 5.10 6.5.6, c'est moi qui souligne):

Dans le cas de p-1, par contre, un objet entier devrait être attribué avant le tableau d'objets que p parcourt, ainsi les boucles qui décrémentent en bas d'un tableau peuvent échouer . Cette restriction permet aux architectures segmentées, par exemple, de placer des objets au début d'un plage de mémoire adressable.

3
Lundin

Dans la plupart des cas, lorsque Stanadrd a classifié une action comme invoquant un comportement indéfini, il l'a fait pour les raisons suivantes:

  1. Il peut y avoir des plates-formes où définir le comportement serait coûteux. Les architectures segmentées pourraient se comporter de manière étrange si le code essayait de faire de l'arithmétique de pointeur allant au-delà des limites des objets, et que certains compilateurs évaluent p > q en testant le signe de q-p.

  2. Il existe certains types de programmes où la définition du comportement serait inutile. De nombreux types de code peuvent très bien se passer sans recourir à des formes d’addition, de soustraction ou de comparaison relationnelle autres que celles données par la norme.

  3. Les personnes qui écrivent des compilateurs à des fins diverses doivent être capables de reconnaître les cas dans lesquels des compilateurs de qualité destinés à de telles fins doivent se comporter de manière prévisible, et de les gérer le cas échéant, que la norme les y oblige ou non.

Les barres n ° 1 et n ° 2 sont des barres très basses, et la n ° 3 a été pensée pour être un "gimme". Bien qu’il soit devenu à la mode pour les rédacteurs de compilateurs de montrer leur intelligence en trouvant des moyens de rompre le code dont le comportement était défini par des implémentations de qualité destinées à la programmation de bas niveau, je ne pense pas que les auteurs du compilateur Standard aient perçu différence entre les actions qui devaient se comporter de manière prévisible, par rapport à celles où presque toutes les implémentations de qualité devaient se comporter de manière identique, mais où il pourrait être utile de laisser certaines implémentations obscures faire autre chose.

2
supercat

Puisque la norme C a pour objectif de couvrir la majorité des architectures de processeur, elle devrait également couvrir celle-ci: ou "descripteurs". Une telle structure contient des informations sur l'objet vers lequel elle pointe (son adresse virtuelle et sa taille) et le décalage qu'il contient. L'ajout ou la soustraction d'un pointeur produit une nouvelle structure avec uniquement le champ de décalage ajusté; la production d'une structure avec un décalage supérieur à la taille de l'objet est interdite. Il existe d'autres restrictions (telles que la façon dont le descripteur initial est généré ou quelles sont les autres manières de le modifier), mais elles ne sont pas pertinentes pour le sujet.

2
Sergey Barannikov

Je voudrais répondre à cela en inversant la question. Au lieu de demander pourquoi l'ajout de pointeur et la plupart des opérations arithmétiques ne sont pas autorisés, pourquoi les pointeurs permettent-ils uniquement d'ajouter ou de soustraire un entier, des incréments post et pré et une décrémentation et une comparaison (ou soustraction) de pointeurs pointant vers le même tableau? Il s’agit de la conséquence logique de l’opération arithmétique . Ajouter/soustraire un entier n à un pointeur p me donne l’adresse du nième élément à partir de l’élément pointé actuellement, dans le sens direct ou inverse. De même, soustraire p1 et p2 pointant vers le même tableau me donne le nombre d'éléments entre les deux pointeurs . Le fait (ou la conception) que les opérations arithmétiques de pointeur sont définies conformément au type de variable pointé est un véritable coup de génie. Toute opération autre que celles autorisées défie la programmation ou le raisonnement philosophique et logique et n'est donc pas autorisée.

1
Seshadri R

(Merci de ne pas voter par-dessus) Ceci est un résumé de la réponse, et la raison pour laquelle j'ai choisi la réponse de Mark Ransom, et pour faire ce post: la réponse est son post + son commentaire suivant.

La question était Quelles sont les raisons de ces limitations (que l'arithmétique et la comparaison de pointeur doivent tomber sur un objet complet unique)?

Commentaire de Ransom + résumé de la réponse: comme il n'y a pas de concept d'espace d'adressage, la seule possibilité de contraindre l'arithmétique de pointeur à tomber dans un espace d'adressage était de le contraindre sur un objet.

Les commentaires de Krazy Glew apportent également une réponse à visée sociologique.

0
Oliv