Pourquoi C a-t-il une distinction entre -> et.?

OK, cela n’a pas de conséquence sérieuse, mais ça me dérange depuis un moment: Y a-t-il une raison pour la distinction entre -> et . les opérateurs?

Bien sûr, la règle actuelle est la suivante . agit sur une structure et -> agit sur un pointeur vers une structure (ou une union). Mais voici comment cela fonctionne dans la pratique. Soit une structure comprenant un élément x , et que ps soit un pointeur sur une structure de même forme.

Si vous écrivez

 s->x 

le compilateur va cracher un avertissement de la manière de

Vous vouliez dire sx Veuillez ressaisir cela et recomstackr.

Si vous écrivez

 ps.x 

le compilateur va cracher un avertissement de la manière de

Vous vouliez dire ps-> x. S’il vous plaît retaper cela et recomstackr.

Comme le compilateur connaît le type de s et de ps au moment de la compilation, il dispose de toutes les informations nécessaires pour interpréter le bon opérateur. Je pense que cela ne ressemble pas à d’autres avertissements (comme un point-virgule manquant), en ce sens qu’il n’y a pas d’ambiguïté sur le correctif correct.

Donc, voici une proposition hypothétique au comité de normalisation C1x (qui ne serait jamais considérée, car l’ISO est sur une lancée conservasortingce):

Compte tenu de l’expression lhs.rhs, si lhs est un type struct ou union, l’expression doit faire référence à l’élément de lhs nommé rhs. Si lhs est de type pointer-to-struct ou -union, alors ceci doit être interprété comme (* lhs) .rhs.

Cela nous sauverait certainement tout le temps et faciliterait l’apprentissage du langage C [et j’ai suffisamment enseigné le C pour dire avec autorité que les apprenants trouvent que -> chose est déroutante ou déroutante.]

Il y a même un précédent où C fait une poignée de choses similaires. Par exemple, pour des raisons d’implémentation, les déclarations de fonction sont toujours converties en pointeur sur fonction, donc f(x,y) et (*f)(x,y) fonctionneront toutes les deux, que f soit déclaré comme fonction ou pointeur Pour fonctionner.

Alors, ma question: quel est le problème avec cette proposition? Pouvez-vous penser à des exemples où il y aurait une ambiguïté fatale entre ps.x et sx , ou pourquoi conserver la distinction obligatoire est autrement utile?

Eh bien, si vous vouliez vraiment introduire ce type de fonctionnalité dans la spécification du langage C, alors, pour le rendre “mélangé” avec le rest du langage, la chose logique à faire serait d’étendre le concept de “décroissance au pointeur”. “à struct types. Vous avez vous-même fait un exemple avec une fonction et un pointeur de fonction. La raison pour laquelle cela fonctionne de cette façon est que le type de fonction dans C décline en type de pointeur dans tous les contextes, sauf pour sizeof et unary & operators. (La même chose arrive aux tableaux, BTW.)

Donc, pour implémenter quelque chose de similaire à ce que vous proposez, nous pourrions introduire le concept de “structure-to-pointter decay”, qui fonctionnerait exactement de la même manière que tous les autres “decays” en C (à savoir array-to -pointer decay et decad de fonction-pointeur) fonctionne: lorsqu’un object struct de type T est utilisé dans une expression, son type se désintègre immédiatement en tapant T* – pointeur vers le début de l’object struct – sauf lorsqu’il s’agit d’un opérande de sizeof ou unary & . Une fois qu’une telle règle de désintégration est introduite pour les structures, vous pouvez utiliser l’opérateur -> pour accéder aux éléments de structure, que vous ayez un pointeur sur struct ou la structure elle-même sur le côté gauche. Opérateur deviendrait complètement inutile dans ce cas (sauf si quelque chose me manque), vous utiliseriez toujours -> et seulement -> .

Ce qui précède, encore une fois, à quoi ressemblerait cette fonctionnalité, à mon avis, si elle était implémentée dans l’esprit du langage C.

Mais je dirais (en accord avec ce que Charles a dit) que la perte de la distinction visuelle entre le code qui fonctionne avec des pointeurs et des structures et le code qui fonctionne avec les structures elles-mêmes n’est pas exactement souhaitable.

PS Une des conséquences négatives évidentes d’une telle règle de désintégration pour les structures serait qu’en plus de l’armée actuelle des débutants croyant de manière désintéressée que «les tableaux ne sont que des indicateurs constants», nous aurions une armée de nouveaux venus “. Et la FAQ de la masortingce de Chris Torek devrait être environ 1,5-2 fois plus grande pour couvrir également les structures 🙂

Je ne pense pas qu’il y ait quelque chose de fou dans ce que vous avez dit. En utilisant . pour les pointeurs vers les structures fonctionnerait.

Cependant, j’aime le fait que les pointeurs vers les structures et les structures sont traités différemment.

Cela donne un contexte aux opérations et aux indices sur ce qui pourrait être coûteux.

Considérez cet extrait, imaginez qu’il se trouve au milieu d’une fonction assez importante.

 sc = 99; f(s); assert(sc == 99); 

Actuellement, je peux dire que s est une structure. Je sais que cela va être copié dans son intégralité pour l’appel à f . Je sais aussi que cela ne peut pas tirer.

Si vous utilisez avec des pointeurs vers struct ont été autorisés, je ne saurais rien de tout cela et l’assertion pourrait tirer, f pourrait mettre sc (err s->c ) à autre chose.

L’autre inconvénient est que cela réduirait la compatibilité avec C ++. C ++ permet -> d’être surchargé par les classes afin que les classes puissent être des pointeurs “similaires”. C’est important que . et -> se comportent différemment. “Nouveau” code C utilisé . avec des pointeurs vers des structures ne serait probablement plus acceptable en tant que code C ++.

Eh bien, il n’y a clairement aucune ambiguïté ou la proposition ne peut être faite. Le seul problème est que si vous voyez:

 p->x = 3; 

vous savez que p est un pointeur mais si vous permettez:

 px = 3; 

dans ce cas, vous ne savez pas réellement, ce qui pourrait créer des problèmes, en particulier si vous lancez plus tard ce pointeur et utilisez le nombre incorrect d’indirection.

Une caractéristique distinctive du langage de programmation C (par opposition à son langage C ++ relatif) est que le modèle de coût est très explicite . Le point se distingue de la flèche car la flèche nécessite une référence mémoire supplémentaire et C prend très soin de rendre évident le nombre de références mémoire du code source.

Eh bien, il pourrait certainement y avoir des cas où vous avez quelque chose de complexe comme:

 (*item)->elem 

(que j’ai eu dans certains programmes), et si vous avez écrit quelque chose comme

 item.elem 

ce qui signifie que elem est un élément d’un élément struct, ou un élément d’une structure pointé par un élément, ou un élément d’une structure pointée comme étant un élément d’une liste pointée par un élément. élément iterator, et ainsi de suite.

Donc oui, cela rend les choses plus claires lorsque vous utilisez des pointeurs vers des pointeurs vers des structures, & c.

Oui, ça va, mais ce n’est pas ce dont C a vraiment besoin

Non seulement c’est correct, mais c’est le style moderne. Java et Go ne font qu’utiliser . . Puisque tout ce qui ne rentre pas dans un registre est une référence à un certain niveau, la distinction entre object et pointeur est définitivement un peu arbitraire, au moins jusqu’à ce que vous obteniez des appels de fonction.

La première étape de l’évolution consistait à rendre l’opérateur de déréférencement postfixé, ce que Dmr impliquait autrefois à un moment donné. Pascal fait ça, il a donc p^.field . La seule raison pour laquelle il y a même un opérateur -> est parce que c’est gênant de devoir taper (*p).field ou p[0].field .

Alors oui, ça marcherait. Ce serait même mieux car cela fonctionne à un niveau d’abstraction plus élevé. On devrait vraiment être capable de faire autant de changements que possible sans avoir besoin de modifier le code en aval, c’est-à-dire en un sens tout le sharepoints langages de haut niveau.

J’ai soutenu que l’utilisation de () pour les appels de fonctions et de [] pour les indices de tableau est erronée. Pourquoi ne pas autoriser différentes implémentations à exporter différentes abstractions?

Mais il n’y a pas beaucoup de raisons de faire le changement. Il est peu probable que les programmeurs se révoltent contre l’absence d’une extension de sucre syntaxique qui sauve un caractère dans une expression et qui serait difficile à utiliser de toute façon, car elle ne serait jamais adoptée si jamais universellement. Rappelez-vous que lorsque les comités de normalisation se déchaînent, ils finissent par prêcher dans des salles vides. Ils nécessitent la coopération et l’accord volontaires des développeurs de compilateurs du monde.

Ce dont C a vraiment besoin n’est pas toujours un moyen plus rapide d’écrire du code dangereux. Cela ne me dérange pas de travailler en C, mais les chefs de projet n’aiment pas que leur fiabilité soit déterminée par leur pire gars, et il est possible que C ait vraiment besoin d’un dialecte sûr, quelque chose comme Cyclone ou quelque chose comme Go .

La syntaxe actuelle permet au lecteur de savoir si le code fonctionne avec un pointeur ou l’object réel. Quelqu’un qui ne connaît pas le code à l’avance le comprend mieux.