128 votes

C++ Renvoi d'une référence à une variable locale

Le code suivant (func1()) est-il correct s'il doit retourner i ? Je me souviens avoir lu quelque part qu'il y a un problème lorsqu'on renvoie une référence à une variable locale. En quoi est-ce différent de func2() ?

int& func1()
{
    int i;
    i = 1;
    return i;
}

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

1 votes

Si vous changez func1() pour utiliser la mémoire allouée dynamiquement, alors ils sont identiques :-) int& i = * new int;

1 votes

Relié à des locaux constants : stackoverflow.com/questions/2784262/

206voto

In silico Points 30778

Cet extrait de code :

int& func1()
{
    int i;
    i = 1;
    return i;
}

ne fonctionnera pas car vous renvoyez un alias (une référence) à un objet dont la durée de vie est limitée à la portée de l'appel de fonction. Cela signifie qu'une fois que func1() retours, int i meurt, ce qui rend la référence renvoyée par la fonction sans valeur car elle se réfère maintenant à un objet qui n'existe pas.

int main()
{
    int& p = func1();
    /* p is garbage */
}

La deuxième version fonctionne car la variable est allouée sur le free store, qui n'est pas lié à la durée de vie de l'appel de fonction. Cependant, vous êtes responsable de delete de l'allocation int .

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

int main()
{
    int* p = func2();
    /* pointee still exists */
    delete p; // get rid of it
}

En règle générale, vous devez envelopper le pointeur dans un fichier RAII et/ou une fonction d'usine afin que vous n'ayez pas à delete vous-même.

Dans les deux cas, vous pouvez simplement retourner la valeur elle-même (bien que je réalise que l'exemple que vous avez fourni était probablement artificiel) :

int func3()
{
    return 1;
}

int main()
{
    int v = func3();
    // do whatever you want with the returned value
}

Notez qu'il est parfaitement possible de renvoyer des objets de grande taille de la même manière. func3() renvoie des valeurs primitives parce qu'à peu près tous les compilateurs implémentent aujourd'hui une certaine forme de optimisation de la valeur de retour :

class big_object 
{ 
public:
    big_object(/* constructor arguments */);
    ~big_object();
    big_object(const big_object& rhs);
    big_object& operator=(const big_object& rhs);
    /* public methods */
private:
    /* data members */
};

big_object func4()
{
    return big_object(/* constructor arguments */);
}

int main()
{
     // no copy is actually made, if your compiler supports RVO
    big_object o = func4();    
}

Il est intéressant de noter que la liaison d'un temporaire à un const la référence est C++ parfaitement légal .

int main()
{
    // This works! The returned temporary will last as long as the reference exists
    const big_object& o = func4();    
    // This does *not* work! It's not legal C++ because reference is not const.
    // big_object& o = func4();  
}

2 votes

Belle explication. :hattip : dans le troisième extrait de code, vous supprimez int* p = func2(); delete p; Maintenant, lorsque vous avez supprimé 'p', est-ce que cela signifie que la mémoire allouée "à l'intérieur" de la fonction func2() La définition de l'auteur a également été supprimée ?

2 votes

@Anisha Kaul : Oui. La mémoire a été allouée dans func2() et libéré à l'extérieur dans la ligne suivante. Il s'agit d'une méthode de gestion de la mémoire plutôt sujette aux erreurs, comme je l'ai dit, vous devriez plutôt utiliser une variante de RAII. Au fait, on dirait que vous apprenez le C++. Je vous recommande de prendre un bon livre d'introduction au C++ à apprendre. En outre, si vous avez une question, vous pouvez toujours la poser sur Stack Overflow. Les commentaires ne sont pas destinés à poser des questions totalement nouvelles.

0 votes

Maintenant j'ai compris, tu as bien fait les choses ! La fonction renvoyait un pointeur, et en dehors de cette fonction, vous avez supprimé la mémoire vers laquelle il pointait. C'est clair maintenant, et merci pour le lien.

21voto

Pica Points 371

Une variable locale est une mémoire sur la pile, cette mémoire n'est pas automatiquement invalidée lorsque vous sortez de la portée. Depuis une fonction plus profondément imbriquée (plus haut sur la pile en mémoire), il est parfaitement sûr d'accéder à cette mémoire.

Une fois que la fonction revient et se termine, les choses deviennent dangereuses. Habituellement, la mémoire n'est pas effacée ou écrasée lorsque vous retournez, ce qui signifie que la mémoire à cette adresse contient toujours vos données - le pointeur semble valide.

Jusqu'à ce qu'une autre fonction accumule la pile et l'écrase. C'est pourquoi cela peut fonctionner pendant un certain temps - puis cesser soudainement de fonctionner après qu'un ensemble de fonctions particulièrement profondément imbriquées, ou une fonction avec une taille vraiment énorme ou de nombreux objets locaux, atteignent à nouveau cette mémoire de pile.

Il peut même arriver que vous atteigniez à nouveau la même partie du programme et que vous écrasiez votre ancienne variable de fonction locale par la nouvelle variable de fonction. Tout ceci est très dangereux et doit être fortement déconseillé. N'utilisez pas de pointeurs vers des objets locaux !

2voto

David Sumich Points 37

Il est bon de se rappeler ces règles simples, et elles s'appliquent à la fois aux paramètres et aux types de retour...

  • Valeur - fait une copie de l'élément en question.
  • Pointeur - fait référence à l'adresse de l'élément en question.
  • Référence - il s'agit littéralement de l'élément en question.

Il y a un temps et un lieu pour chacun d'entre eux, alors assurez-vous d'apprendre à les connaître. Les variables locales, comme vous l'avez montré ici, ne sont que cela, limitées au temps où elles sont localement vivantes dans la portée de la fonction. Dans votre exemple, le type de retour est int* et renvoyant &i aurait été tout aussi incorrect. Vous seriez mieux dans ce cas en faisant ceci...

void func1(int& oValue)
{
    oValue = 1;
}

Ce faisant, vous modifiez directement la valeur du paramètre que vous avez transmis. Alors que ce code...

void func1(int oValue)
{
    oValue = 1;
}

ne le ferait pas. Il changerait juste la valeur de oValue local à l'appel de fonction. La raison en est que vous ne modifiez en fait qu'une copie "locale" de la fonction oValue et non oValue lui-même.

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