web-dev-qa-db-fra.com

déréférencer un pointeur lors du passage par référence

que se passe-t-il lorsque vous déréférencer un pointeur lors du passage par référence à une fonction?

Voici un exemple simple

int& returnSame( int &example ) { return example; }

int main()
{
  int inum = 3;
  int *pinum = & inum;

  std::cout << "inum: " <<  returnSame(*pinum) << std::endl;

  return 0;          

}

Y a-t-il un objet temporaire produit?

42
MWright

Le déréférencement du pointeur ne crée pas de copie; il crée un lvalue qui fait référence à la cible du pointeur. Cela peut être lié à l'argument de référence lvalue, et donc la fonction reçoit une référence à l'objet vers lequel pointe le pointeur et renvoie une référence au même. Ce comportement est bien défini et aucun objet temporaire n'est impliqué.

S'il prenait l'argument par valeur, cela créerait une copie locale et renvoyer une référence à cela serait mauvais, donnant un comportement indéfini s'il était accédé.

39
Mike Seymour

La réponse à votre question écrite

Non, ce comportement est défini. Aucun constructeur n'est appelé lorsque les types de pointeur sont déréférencés ou que des types de référence sont utilisés, sauf indication explicite du programmeur, comme avec l'extrait de code suivant, dans lequel l'opérateur new appelle le constructeur par défaut pour le type int .

int* variable = new int;

Quant à ce qui se passe réellement, tel qu'écrit, returnSame(*pinum) est la même variable que inum. Si vous souhaitez vérifier cela vous-même, vous pouvez utiliser l'extrait de code suivant:

returnSame(*pinum) = 10;
std::cout << "inum: " << inum << std::endl;

Analyse approfondie

Je vais commencer par corriger votre code fourni, qu'il ne semble pas que vous ayez essayé de compiler avant de le publier. Après les modifications, la seule erreur restante se trouve sur la première ligne:

int& returnSame( int &example ) { return example; } // semi instead of colon

Pointeurs et références

Les pointeurs et les références sont traités de la même manière par le compilateur, ils diffèrent par leur utilisation, pas tant par leur implémentation. Les types de pointeur et les types de référence stockent, en tant que valeur, l'emplacement d'autre chose. Le déréférencement de pointeur (à l'aide des opérateurs * Ou ->) Demande au compilateur de produire du code pour suivre le pointeur et d'effectuer l'opération sur l'emplacement auquel il se réfère plutôt que sur la valeur elle-même. Aucune nouvelle donnée n'est allouée lorsque vous déréférencer un pointeur (aucun constructeur n'est appelé).

L'utilisation des références fonctionne de la même manière, sauf que le compilateur suppose automatiquement que vous voulez la valeur à l'emplacement plutôt qu'à l'emplacement lui-même. En fait, il est impossible de se référer à l'emplacement spécifié par une référence de la même manière que les pointeurs vous permettent: une fois affectée, une référence ne peut pas être réinstallée (modifiée) (c'est-à-dire sans se fier à un comportement indéfini), cependant vous pouvez toujours obtenir sa valeur en utilisant l'opérateur & dessus. Il est même possible d'avoir une référence NULL, bien que leur manipulation soit particulièrement délicate et je ne recommande pas de les utiliser.

Analyse d'extraits

int *pinum = & inum;

Crée un pointeur pointant vers une variable existante, inum. La valeur du pointeur est l'adresse mémoire dans laquelle inum est stocké. La création et l'utilisation de pointeurs n'appelleront PAS implicitement un constructeur pour un objet pointé, JAMAIS. Cette tâche est laissée au programmeur.

*pinum

Déréférencer un pointeur produit effectivement une variable régulière. Cette variable peut occuper conceptuellement le même espace qu'une autre variable nommée utilise, ou non. dans ce cas, *pinum et inum sont la même variable. Quand je dis "produit", il est important de noter qu'aucun constructeur n'est appelé. C'est pourquoi vous DEVEZ initialiser les pointeurs avant de les utiliser: le déréférencement de pointeurs n'allouera JAMAIS de stockage.

returnSame(*pinum)

Cette fonction prend une référence et renvoie la même référence. Il est utile de réaliser que cette fonction peut également être écrite avec des pointeurs et se comporter exactement de la même manière. Les références n'effectuent aucune initialisation non plus, dans la mesure où elles n'appellent pas de constructeurs. Cependant, il est illégal d'avoir une référence non initialisée, donc courir dans la mémoire non initialisée à travers eux n'est pas une erreur aussi courante qu'avec les pointeurs. Votre fonction peut être réécrite pour utiliser des pointeurs de la manière suivante:

int* returnSamePointer( int *example ) { return example; }

Dans ce cas, vous n'auriez pas besoin de déréférencer le pointeur avant de le passer, mais vous auriez besoin de déréférencer la valeur de retour de la fonction avant de l'imprimer:

std::cout << "inum: " <<  *(returnSamePointer(pinum)) << std::endl;

Références NULES

La déclaration d'une référence NULL est dangereuse, car toute tentative d'utilisation tentera automatiquement de la déférencer, ce qui entraînera une erreur de segmentation. Vous pouvez cependant vérifier en toute sécurité si une référence est une référence nulle. Encore une fois, je recommande fortement de ne jamais les utiliser.

int& nullRef = *((int *) NULL);      // creates a reference to nothing
bool isRefNull = (&nullRef == NULL); // true

Résumé

  • Les types pointeur et référence sont deux façons différentes d'accomplir la même chose
  • La plupart des pièges qui s'appliquent à l'un s'appliquent à l'autre
  • Ni les pointeurs ni les références n'appelleront implicitement des constructeurs ou des destructeurs de valeurs référencées en aucune circonstance
  • La déclaration d'une référence à un pointeur déréférencé est parfaitement légale, tant que le pointeur est correctement initialisé
27
Wug

Un compilateur n'appelle rien. Il génère simplement du code. Déréférencer un pointeur correspondrait au niveau le plus élémentaire à une sorte d'instruction de chargement, mais dans le code actuel, le compilateur peut facilement optimiser cela et simplement imprimer la valeur directement, ou peut-être directement un raccourci pour charger inum.

Concernant votre "objet temporaire": Déréférencer un pointeur donne toujours une valeur l.

Peut-être y a-t-il une question plus intéressante cachée dans votre question: comment le compilateur implémente-t-il le passage d'arguments de fonction comme références?

1
Kerrek SB