2010-05-26 4 views
16

J'ai toujours voulu savoir quelle est la vraie différence entre la façon dont le compilateur voit un pointeur sur une structure (en supposé C) et une structure elle-même.Vraie chose à propos de "->" et "."

struct person p; 
struct person *pp; 

pp->age, je pense toujours que le compilateur ne: "valeur de p + offset de atribute "âge" dans le struct". Mais que fait-il avec person.p? Ce serait presque pareil. Pour moi "le programmeur", p n'est pas une adresse mémoire, c'est comme "la structure elle-même", mais bien sûr ce n'est pas la façon dont le compilateur gère cela.

Je pense que c'est plus une chose syntaxique, et le compilateur fait toujours (&p)->age.

J'ai raison?

+1

Pour ne pas détourner la question, mais en tant qu'étudiant, je suis curieux de savoir dans quel domaine on apprendrait sur ce genre de chose. Conception de compilateur? (Je veux dire, évidemment, j'ai appris (* p) .q est le même que p-> q dans ma classe C++) – Adam

+0

Habituellement, un cours de systèmes informatiques de bas niveau (le genre qui traite de l'allocation de mémoire, cadres de pile, etc.) . – Amber

+0

Merci - je suis seulement un étudiant en deuxième année, donc je suis sûr que nous y arriverons finalement – Adam

Répondre

28

p->q est essentiellement le sucre syntaxique pour (*p).q en ce qu'il déréférence le pointeur p puis se rend au champ approprié q à l'intérieur. Il économise le typage pour un cas très commun (pointeurs vers les structures).

Essentiellement, ->-t deux déférences (pointeur déréférencer, déréférencement sur le terrain), tandis que . ne fournit qu'un seul (déréférencement sur le terrain). En raison du facteur de déréférencement multiple, -> ne peut pas être complètement remplacé par une adresse statique par le compilateur et inclura toujours au moins un calcul d'adresse (les pointeurs peuvent changer de façon dynamique à l'exécution, donc les emplacements changeront aussi), alors que dans certains cas, les opérations . peuvent être remplacées par le compilateur avec un accès à une adresse fixe (puisque l'adresse de la structure de base peut également être fixée).

+1

+1 pour "sucre syntaxique" (et une bonne explication) – Adam

+0

Ok. Mais ma question est ce qui est exactement "p" pour le compilateur ?, c'est aussi une adresse, n'est-ce pas ?. Mais pour moi le programmeur est "la structure elle-même" – fsdfa

+0

En supposant que 'p' est une variable locale, c'est un décalage dans la pile. – Amber

5

Mise à jour (voir les commentaires):

Vous avez la bonne idée, mais il y a une différence importante pour les variables globales et statiques: lorsque le compilateur voit p.age pour une variable globale ou statique , il peut le remplacer, au moment de la compilation, par l'adresse exacte du champ age dans la structure.

En revanche, pp->agedoit être compilé comme « valeur de p + offset de age champ », puisque la valeur de p peut changer lors de l'exécution.

+2

S'il est déclaré sur la pile, il ne connaît pas l'adresse exacte au moment de la compilation, seul le décalage par rapport au pointeur de la pile. – cjg

+0

Quand il voit p.age, il connaît le ** offset ** au sein de la structure, mais il ne connaît pas (sauf s'il s'agit d'une variable globale) l'adresse absolue de la structure. – ChrisW

+0

@cjg et @ChrisW: Bons points. Je vais ajuster ma réponse. –

1

Puisque p est une variable locale (automatique), elle est stockée dans la pile. Par conséquent, le compilateur y accède en termes de décalage par rapport au pointeur de pile (SP) ou au pointeur de trame (FP ou BP, dans les architectures où il existe). En revanche, * p fait référence à une adresse de mémoire [habituellement] allouée dans le tas, de sorte que les registres de pile ne sont pas utilisés.

+1

Vous pouvez passer une structure structurée par une pile à un sous-programme: dans ce cas, le pointeur reçu par le sous-programme est à un objet de la pile. – ChrisW

+0

@ChrisW: Bon point. Bien sûr, le compilateur accède toujours à la structure en tant qu'adresse directe (sans utiliser SP). –

3

Les deux instructions ne sont pas équivalentes, même à partir de la "perspective du compilateur". La déclaration p.age se traduit à l'adresse de p + le décalage de age, tout en pp->age se traduit à l'adresse contenue danspp + le décalage de age.variables

L'adresse de une variable et l'adresse contenue dans un (pointeur) sont des choses très différentes.

Dites le décalage de l'âge est 5. Si p est une structure, son adresse peut-être 100, si p.age références portent sur 105.

Mais si pp est un pointeur vers une structure, son adresse peut être 100, mais la valeur stockée à l'adresse 100 n'est pas le début d'une structure person, c'est un pointeur. Ainsi, la valeur à l'adresse 100 (l'adresse contenue danspp) peut être, par exemple, 250. Dans ce cas, les références pp->age adresse 255, pas 105.

+0

En d'autres termes, la lecture de 'p.age' nécessite conceptuellement une lecture de la mémoire, à partir de l'emplacement (connu) de' p' plus le décalage de 'age'. Alors que la lecture de 'pp-> age' nécessite conceptuellement deux lectures de mémoire - une de l'emplacement de' pp', puis une seconde de l'emplacement donné par la première lecture plus le décalage de 'age'. – caf

0

Dans les deux cas, la structure et ses membres sont traités par

adresse (personne) + offset (âge)

utilisation du p avec un struct stocké dans la mémoire de la pile donne le compilateur plus d'options pour optimiser l'utilisation de la mémoire. Il pourrait stocker l'âge seulement, au lieu de la structure entière si rien d'autre n'est utilisé - ceci fait échouer l'adressage avec la fonction ci-dessus (je pense que lire l'adresse d'une structure arrête cette optimisation).
Une structure sur la pile peut ne pas avoir d'adresse mémoire du tout. Si la structure est assez petite et ne vit que peu de temps, elle peut être mappée sur certains registres des processeurs (comme pour l'optimisation ci-dessus pour lire l'adresse).

La réponse courte: lorsque le compilateur n'optimise pas, vous avez raison. Dès que le compilateur commence à optimiser uniquement ce que la norme c spécifie est garanti. Edit: Suppression de l'emplacement de pile/heap défectueux pour "pp->" puisque la structure pointée vers la structure peut être à la fois sur le tas et sur la pile.

+0

L'emplacement de la variable n'a * rien * à voir avec le mode d'adressage utilisé pour y accéder. Avec 'pp->' la structure pourrait être n'importe où, tas, pile, "global". Avec 'p.', la structure peut être dans n'importe lequel des éléments ci-dessus, sauf pour la mémoire de tas ('cos vous ne pouvez pas y déclarer des variables directement). J'aime toujours penser aux pointeurs comme étant des flèches, pointant vers des tableaux de cellules de mémoire. Comme analogies vont, il semble facile et pas si loin faux. –

+0

@ FellowsDon vous avez raison sur "pp->" Je n'ai pensé qu'au cas d'utilisation le plus simple. Si l'âge était une structure, vous pourriez l'adresser avec pp-> age.days de sorte que le "." ne parle pas beaucoup de l'emplacement de la mémoire non plus. – josefx

+0

C'est vrai. La différence n'est pas liée à la localisation, mais plutôt si vous avez le nom d'une chose (c'est-à-dire un pointeur) ou la chose elle-même (la cellule de mémoire/structure/tableau/...) dans la philosophie kantienne. sich "). –

1

C'est une question que je me suis toujours posée.

v.x, l'opérateur membre , est valide uniquement pour struct. v->x, l'opérateur du pointeur, est valide uniquement pour les pointeurs de structure.

Alors pourquoi avoir deux opérateurs différents, car un seul est nécessaire? Par exemple, seul l'opérateur . peut être utilisé; le compilateur connaît toujours le type de v, donc il sait quoi faire: v.x si v est une struct, (*v).x si v est un pointeur struct.

J'ai trois théories:

  • imprévoyance temporaire par K & R (dont la théorie que je voudrais être faux)
  • qui rend le travail plus facile pour le compilateur (une théorie pratique, compte tenu de la conception temps de C :)
  • lisibilité (dont la théorie que je préfère)

Malheureusement, je ne sais pas lequel (le cas échéant) est vrai.

Questions connexes