L' .
opérateur point ( ) est utilisé pour accéder à un membre d'une structure, tandis que l'opérateur flèche ( ->
) dans C est utilisé pour accéder à un membre d'une structure qui est référencé par le pointeur en question.
Le pointeur lui-même n'a pas de membres accessibles avec l'opérateur point (c'est en fait seulement un nombre décrivant un emplacement dans la mémoire virtuelle donc il n'a pas de membres). Ainsi, il n'y aurait aucune ambiguïté si nous venions de définir l'opérateur point pour déréférencer automatiquement le pointeur s'il est utilisé sur un pointeur (une information connue du compilateur au moment de la compilation afaik).
Alors pourquoi les créateurs de langage ont-ils décidé de compliquer les choses en ajoutant cet opérateur apparemment inutile? Quelle est la grande décision de conception?
la source
.
opérateur a-t-il une priorité plus élevée que l'*
opérateur? Sinon, nous pourrions avoir * ptr.member et var.member.Réponses:
J'interpréterai votre question comme deux questions: 1) pourquoi
->
existe-t-il, et 2) pourquoi.
ne déréférence pas automatiquement le pointeur. Les réponses aux deux questions ont des racines historiques.Pourquoi
->
existe- t-il même?Dans l'une des toutes premières versions du langage C (que j'appellerai CRM pour " C Reference Manual ", fourni avec 6th Edition Unix en mai 1975), l'opérateur
->
avait une signification très exclusive, non synonyme de combinaison*
et.
Le langage C décrit par CRM était très différent du C moderne à bien des égards. Dans la structure CRM, les membres ont implémenté le concept global de décalage d'octets , qui peut être ajouté à n'importe quelle valeur d'adresse sans restriction de type. C'est-à-dire que tous les noms de tous les membres de la structure avaient une signification globale indépendante (et devaient donc être uniques). Par exemple, vous pouvez déclarer
et nom
a
représenterait le décalage 0, tandis que le nomb
représenterait le décalage 2 (en supposant leint
type de taille 2 et pas de remplissage). La langue exigeait que tous les membres de toutes les structures de l'unité de traduction aient des noms uniques ou représentent la même valeur de décalage. Par exemple, dans la même unité de traduction, vous pouvez également déclareret ce serait OK, car le nom
a
représenterait systématiquement le décalage 0. Mais cette déclaration supplémentaireserait formellement invalide, car il a tenté de "redéfinir"
a
comme décalage 2 etb
comme décalage 0.Et c'est là que l'
->
opérateur entre en jeu. Étant donné que chaque nom de membre struct avait sa propre signification globale auto-suffisante, le langage supportait des expressions comme celles-ciLa première affectation a été interprétée par le compilateur comme "prendre l'adresse
5
, lui ajouter un décalage2
et l'affecter42
à laint
valeur à l'adresse résultante". C'est-à-dire que ce qui précède attribuerait42
uneint
valeur à l'adresse7
. Notez que cette utilisation de->
ne se souciait pas du type de l'expression sur le côté gauche. Le côté gauche a été interprété comme une adresse numérique de valeur (que ce soit un pointeur ou un entier).Ce genre de ruse n'était pas possible avec
*
et en.
combinaison. Tu ne pouvais pas fairepuisque
*i
est déjà une expression invalide. L'*
opérateur, puisqu'il est distinct de.
, impose des exigences de type plus strictes à son opérande. Pour fournir une capacité de contourner cette limitation, CRM a introduit l'->
opérateur, qui est indépendant du type de l'opérande de gauche.Comme Keith l'a noté dans les commentaires, cette différence entre
->
et*
+.
combinaison est ce que CRM appelle «l'assouplissement de l'exigence» dans 7.1.8: à l' exception de l'assouplissement de l'exigence quiE1
est de type pointeur, l'expressionE1−>MOS
est exactement équivalente à(*E1).MOS
Plus tard, dans K&R C, de nombreuses fonctionnalités initialement décrites dans CRM ont été retravaillées de manière significative. L'idée de "membre struct comme identificateur de décalage global" a été complètement supprimée. Et la fonctionnalité de l'
->
opérateur est devenue entièrement identique à la fonctionnalité*
et à la.
combinaison.Pourquoi ne pouvez-vous pas
.
déréférencer le pointeur automatiquement?Encore une fois, dans la version CRM de la langue, l'opérande gauche de l'
.
opérateur devait être une valeur l . C'était la seule exigence imposée à cet opérande (et c'est ce qui le rendait différent->
, comme expliqué ci-dessus). Notez que CRM ne nécessitait pas l'opérande gauche de.
pour avoir un type struct. Il fallait juste que ce soit une lvalue, n'importe quelle lvalue. Cela signifie que dans la version CRM de C, vous pouvez écrire du code comme celui-ciDans ce cas, le compilateur écrirait
55
dans uneint
valeur positionnée à 2 octets dans le bloc de mémoire continuec
, même si typestruct T
n'avait pas de champ nomméb
. Le compilateur ne se soucierait pas du tout du type réelc
. Tout ce quic
importait, c'était une valeur l: une sorte de bloc de mémoire inscriptible.Notez maintenant que si vous avez fait cela
le code serait considéré comme valide (car il
s
s'agit également d'une valeur l) et le compilateur tenterait simplement d'écrire des données dans le pointeurs
lui - même , à l'octet-offset 2. Inutile de dire que des choses comme celle-ci pourraient facilement entraîner un dépassement de mémoire, mais le langage ne se préoccupe pas de ces questions.C'est-à-dire que dans cette version du langage, votre idée proposée de surcharger l'opérateur
.
pour les types de pointeurs ne fonctionnerait pas: l'opérateur.
avait déjà une signification très spécifique lorsqu'il était utilisé avec des pointeurs (avec des pointeurs lvalue ou avec n'importe quelle valeur l). C'était une fonctionnalité très étrange, sans aucun doute. Mais c'était là à l'époque.Bien sûr, cette fonctionnalité étrange n'est pas une raison très forte contre l'introduction d'un
.
opérateur surchargé pour les pointeurs (comme vous l'avez suggéré) dans la version retravaillée de C - K&R C. Mais cela n'a pas été fait. Peut-être qu'à cette époque, un code hérité écrit dans la version CRM de C devait être pris en charge.(L'URL du Manuel de référence de 1975 C n'est peut-être pas stable. Une autre copie, peut-être avec quelques différences subtiles, est ici .)
la source
*i
pas d'une valeur l d'un type par défaut (int?) À l'adresse 5? Alors (* i) .b aurait fonctionné de la même manière.struct stat
) préfixent leurs champs (par exemplest_mode
).Au-delà des raisons historiques (bonnes et déjà signalées), il y a aussi un petit problème avec la priorité des opérateurs: l'opérateur point a une priorité plus élevée que l'opérateur étoile, donc si vous avez struct contenant pointeur vers struct contenant pointeur vers struct ... Ces deux sont équivalents:
Mais le second est clairement plus lisible. L'opérateur flèche a la priorité la plus élevée (tout comme le point) et s'associe de gauche à droite. Je pense que c'est plus clair que d'utiliser l'opérateur point à la fois pour les pointeurs vers struct et struct, car nous connaissons le type de l'expression sans avoir à regarder la déclaration, qui pourrait même être dans un autre fichier.
la source
a.b.c.d
comme(*(*(*a).b).c).d
, rendant l'->
opérateur inutile. La version (a.b.c.d
) de l'OP est donc également lisible (par rapport àa->b->c->d
). C'est pourquoi votre réponse ne répond pas à la question du PO.a.b.c.d
eta->b->c->d
comme deux choses très différentes: La première est un accès mémoire unique à un sous-objet imbriqué (il n'y a qu'un seul objet mémoire dans ce cas ), le second est constitué de trois accès à la mémoire, poursuivant des pointeurs à travers quatre objets probablement distincts. C'est une énorme différence dans la disposition de la mémoire, et je pense que C a raison de distinguer très clairement ces deux cas.C fait également du bon travail pour ne rien rendre ambigu.
Bien sûr, le point pourrait être surchargé pour signifier les deux choses, mais la flèche s'assure que le programmeur sait qu'il fonctionne sur un pointeur, tout comme lorsque le compilateur ne vous permet pas de mélanger deux types incompatibles.
la source