326 votes

Pourquoi est-ce que la flèche (opérateur dans C->) existe ?

Le point (.) l'opérateur est utilisé pour accéder à un membre d'une structure, tandis que la flèche de l'opérateur (->) 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 tous les membres qui pourrait être accessible avec l'opérateur point (c'est en fait seulement un certain nombre de décrire un emplacement dans la mémoire virtuelle, donc il n'a pas de membres). Donc, il n'y aurait pas d'ambiguïté si nous venons de définir l'opérateur point automatiquement déréférencer le pointeur sur un pointeur (une information qui est connu pour le compilateur au moment de la compilation autant que je sache).

Pourquoi la langue créateurs ont décidé de compliquer les choses en ajoutant ce qui semble inutile de l'opérateur? Quelle est la grande décision de conception?

417voto

AndreyT Points 139512

Je vais vous interpréter votre question à deux questions: 1) pourquoi est - -> il existe encore, et 2) pourquoi est - . ne met pas automatiquement à déréférencer le pointeur. Les réponses à ces deux questions ont des racines historiques.

Pourquoi est - -> même exister?

Dans une des premières versions du langage C (je ferai référence en tant que CRM pour "C Manuel de Référence"), l'opérateur -> avait très exclusif sens, ne sont pas synonymes * et . combinaison

Dennis M. Ritchie - C Manuel De Référence

Le langage C décrit par le CRM a été très différente de la moderne C à bien des égards. En CRM les membres de la structure mis en œuvre le concept global de décalage, c'est à dire tous les noms de tous les membres de la structure avait indépendant sens global (et, par conséquent, devait être unique). Par exemple, vous pourriez déclarer

struct S {
  int a;
  int b;
};

et nom a tiendrait à l'offset 0, tandis que le nom de l' b se offset 2 (en supposant int type de taille 2 et aucun rembourrage). La langue exigé que tous les membres de toutes les structures dans l'unité de traduction, soit ont des noms uniques ou de stand pour la même valeur de décalage. E. g. dans la même unité de traduction, vous pouvez en plus de déclarer

struct X {
  int a;
  int x;
};

et qui serait OK, depuis que le nom de l' a serait constamment stand de décalage 0. Mais cette déclaration supplémentaire

struct Y {
  int b;
  int a;
};

formellement valide, puisqu'il a tenté de "redéfinir" a offset 2 et b offset 0.

Et c'est là que l' -> de l'opérateur. Puisque chaque struct nom de membre avait sa propre auto-suffisante sens, la langue prise en charge des expressions comme ces

int i = 5;
i->b = 42;  /* Write 42 into `int` at address 7 */
100->a = 0; /* Write 0 into `int` at address 100 */

La première mission a été interprété par le compilateur comme "prendre l'adresse 5, ajoutez un décalage 2 à et de lui attribuer 42 de la int de la valeur à la résultante de l'adresse". I. e. ci-dessus à attribuer, 42 de int de la valeur à l'adresse 7. Notez que cette utilisation de l' -> ne se soucient pas le type de l'expression sur le côté gauche. Le côté gauche a été interprété comme une rvalue adresse numérique (que ce soit un pointeur ou un entier).

Ce genre de supercherie n'était pas possible avec * et . combinaison. Vous ne pourriez pas faire

(*i).b = 42;

depuis *i est déjà une défaillance de l'expression. L' * de l'opérateur, depuis qu'il est séparé de ., impose plus strictes en matière de type de son opérande. Pour fournir une capacité permettant de contourner cette limitation CRM introduit l' -> opérateur, qui est indépendant du type de la partie gauche de l'opérande.

Comme Keith noté dans les commentaires, cette différence entre -> et *+. combinaison est ce que le CRM est, en se référant à "la relaxation de l'exigence" dans 7.1.8: Sauf pour l'assouplissement de l'exigence d' E1 être de type pointeur, l'expression E1−>MOS est exactement équivalent à (*E1).MOS

Plus tard, dans le K&R C de nombreuses fonctionnalités initialement décrite dans les CRM ont été considérablement retravaillé. L'idée de "struct membre de global offset identificateur" a été entièrement supprimé. Et la fonctionnalité de l' -> opérateur est devenu totalement identique à la fonctionnalité de l' * et . combinaison.

Pourquoi ne peut - . déréférencer le pointeur automatiquement?

Encore une fois, dans CRM version de la langue de l'opérande gauche de l' . de l'opérateur devait être une lvalue. Que c'était la seule obligation que lui impose l'opérande (et c'est ce qui le rend différent de ->, comme expliqué ci-dessus). Notez que le CRM n'a pas besoin de l'opérande gauche de l' . d'avoir un type struct. Il a juste tenu à être une lvalue, tout lvalue. Cela signifie que dans le CRM version de C, vous pouvez écrire le code comme ceci

struct S { int a, b; };
struct T { float x, y, z; };

struct T c;
c.b = 55;

Dans ce cas, le compilateur pourrait écrire 55 en int de la valeur positionnée à l'octet d'offset de 2 dans la mémoire continue de bloquer connu comme c, même si le type struct T n'avait pas de champ nommé b. Le compilateur ne serait pas de soins sur le type réel de l' c . Tous il se souciait est qu' c est une lvalue: une sorte d'écriture de bloc de mémoire.

Maintenant, notez que si vous avez fait cela

S *s;
...
s.b = 42;

le code serait considéré comme valide (depuis s est également une lvalue) et le compilateur aurait simplement tenter d'écrire des données dans le pointeur s lui-même, à l'octet d'offset de 2. Inutile de dire, ce genre de choses pourrait facilement provoquer de dépassement de capacité de la mémoire, mais la langue n'a pas à se préoccuper de ces questions.

I. e. dans la version de la langue de votre idée à propos de la surcharge de l'opérateur . pour les types pointeur ne fonctionnerait pas: opérateur . avaient déjà sens très précis lorsqu'il est utilisé avec des pointeurs (avec lvalue pointeurs ou avec toute lvalues à tous). C'était très bizarre fonctionnalité, sans aucun doute. Mais il était là à l'époque.

Bien sûr, cela bizarre fonctionnalité n'est pas une très bonne raison contre l'introduction surchargé . de l'opérateur pour les pointeurs (comme vous l'avez suggéré) dans la version remaniée de C - K&R C. Mais il n'a pas été fait. Peut-être qu'à cette époque il y avait un code existant écrit en CRM version de C qui a dû être pris en charge...

54voto

effeffe Points 1600

Au-delà de l'histoire (bon et déjà signalé) raisons, il y a aussi un petit problème avec les opérateurs de préséance: l'opérateur de point a une plus grande priorité que l'étoile de l'opérateur, donc si vous avez de la structure contenant le pointeur de structure contenant pointeur vers une struct... Ces deux sont équivalentes:

(*(*(*a).b).c).d

a->b->c->d

Mais la deuxième est nettement plus lisible. Flèche de l'opérateur a la priorité la plus élevée (comme dot) et les associés de la gauche vers la droite. Je pense que c'est plus clair que l'utilisation de l'opérateur de point à la fois pour des pointeurs vers des struct struct et, parce que nous connaissons le type de l'expression sans avoir à regarder dans la déclaration, qui pourrait même être dans un autre fichier.

-1voto

Charles Dowd Points 351

Tout simplement : Le «-> « est effectivement un moyen d’accéder à un objet Mutable Think sur ». » comme « lu » et «-> » en « écriture » et praticable pour les fonctions et méthodes ! Il est toujours important de connaître explicitement si vous faites affaire avec des pointeurs ou pas en C/C++.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X