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

116

Le code suivant (func1 ()) est-il correct s'il doit renvoyer i? Je me souviens avoir lu quelque part qu'il y avait un problème lors du renvoi d'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;
}
blitzkriegz
la source
1
Si vous changez func1 () pour utiliser de la mémoire allouée dynamiquement, ils sont les mêmes :-)int& i = * new int;
Martin York
1
Lié pour les locaux const: stackoverflow.com/questions/2784262/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

193

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 avec une durée de vie limitée à la portée de l'appel de fonction. Cela signifie qu'une fois func1()retourne, int imeurt, ce qui rend la référence retournée par la fonction sans valeur car elle fait maintenant référence à 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 magasin gratuit, qui n'est pas lié à la durée de vie de l'appel de fonction. Cependant, vous êtes responsable de la deletesomme allouée 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 encapsulez le pointeur dans une classe RAII et / ou une fonction d'usine afin que vous n'ayez pas à le deletefaire vous-même.

Dans les deux cas, vous pouvez simplement renvoyer 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 bien de renvoyer de gros objets de la même manière que func3()les valeurs primitives, car à peu près tous les compilateurs implémentent de nos jours une forme d' 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();    
}

Fait intéressant, lier un temporaire à une référence const est parfaitement légal en C ++ .

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();  
}
In silico
la source
2
Belle explication. : hattip: Dans le troisième extrait de code, vous supprimez int* p = func2(); delete p;Maintenant, lorsque vous avez supprimé 'p', cela signifie-t-il que la mémoire allouée "à l'intérieur" de la func2()définition de la fonction a également été supprimée?
Aquarius_Girl
2
@Anisha Kaul: Oui. La mémoire a été allouée à l'intérieur func2()et libérée à l'extérieur dans la ligne suivante. C'est une façon plutôt sujette aux erreurs de gérer la mémoire, comme je l'ai dit, vous utiliseriez plutôt une variante de RAII. Au fait, vous semblez apprendre le C ++. Je recommande de prendre un bon livre d'introduction au C ++ pour apprendre. De plus, pour référence future si vous avez une question, vous pouvez toujours la publier sur Stack Overflow. Les commentaires ne sont pas destinés à poser des questions totalement nouvelles.
In silico
Maintenant j'ai compris, vous avez bien fait les choses! La fonction renvoyait un pointeur et en dehors de cette fonction, vous avez supprimé la mémoire vers laquelle elle pointait. C'est clair maintenant, et merci pour le lien.
Aquarius_Girl
et vous avez édité la réponse ?? : mad: J'aurais pu le manquer facilement. ;);)
Aquarius_Girl
@Anisha Kaul: Non, je ne l'ai pas fait. La dernière fois que j'ai édité ma réponse, c'était le 10 janvier, selon l'horodatage sous mon poste.
In silico
18

Une variable locale est la mémoire sur la pile, cette mémoire n'est pas automatiquement invalidée lorsque vous sortez de la portée. À partir d'une fonction imbriquée plus profondément (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 supprimée ou écrasée lorsque vous revenez, ce qui signifie que la mémoire à ces adresses contient toujours vos données - le pointeur semble valide.

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

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

Pica
la source
2

Une bonne chose à retenir sont 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 - est littéralement l'élément en question.

Il y a un moment et un lieu pour chacun, alors assurez-vous de les connaître. Les variables locales, comme vous l'avez montré ici, ne sont que cela, limitées au temps où elles sont localement actives dans la portée de la fonction. Dans votre exemple, avoir un type de int*retour et un retour &iaurait été tout aussi incorrect. Vous feriez mieux dans ce cas de faire cela ...

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

Cela changerait directement la valeur de votre paramètre passé. Alors que ce code ...

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

pas. Cela changerait simplement la valeur de oValuelocal à l'appel de fonction. La raison en est que vous ne changeriez en fait qu'une copie "locale" de oValue, et non oValuelui-même.

David Sumich
la source