C ++ Rückgabe eines Verweises auf eine lokale Variable

115

Ist der folgende Code (func1 ()) korrekt, wenn er i zurückgeben muss? Ich erinnere mich, dass ich irgendwo gelesen habe, dass es ein Problem gibt, wenn ein Verweis auf eine lokale Variable zurückgegeben wird. Wie unterscheidet es sich von func2 ()?

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

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}
Blitzkriegz
quelle
1
Wenn Sie func1 () ändern, um dynamisch zugewiesenen Speicher zu verwenden, sind sie dieselben :-)int& i = * new int;
Martin York
1
Verwandte für const Einheimische: stackoverflow.com/questions/2784262/…
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

Antworten:

192

Dieser Code-Ausschnitt:

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

funktioniert nicht, da Sie einen Alias ​​(eine Referenz) an ein Objekt zurückgeben, dessen Lebensdauer auf den Umfang des Funktionsaufrufs beschränkt ist. Das heißt, sobald func1()zurückgegeben wird, int istirbt, wird die von der Funktion zurückgegebene Referenz wertlos, da sie jetzt auf ein Objekt verweist, das nicht existiert.

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

Die zweite Version funktioniert, da die Variable im freien Speicher zugeordnet ist, der nicht an die Lebensdauer des Funktionsaufrufs gebunden ist. Sie sind jedoch für deletedie Zuweisung verantwortlich 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
}

Normalerweise wird der Zeiger in eine RAII- Klasse und / oder eine Factory-Funktion eingeschlossen, damit Sie ihn nicht deleteselbst benötigen .

In beiden Fällen können Sie einfach den Wert selbst zurückgeben (obwohl mir klar ist, dass das von Ihnen angegebene Beispiel wahrscheinlich erfunden wurde):

int func3()
{
    return 1;
}

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

Beachten Sie, dass es vollkommen in Ordnung ist, große Objekte auf dieselbe Weise zurückzugeben, wie func3()primitive Werte zurückgegeben werden, da heutzutage fast jeder Compiler eine Form der Rückgabewertoptimierung implementiert :

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();    
}

Interessanterweise ist das Binden eines Temporärs an eine const- Referenz in C ++ völlig legal .

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
quelle
2
Schöne Erklärung. : hattip: Im dritten Code-Snippet löschen int* p = func2(); delete p;Sie. Wenn Sie nun 'p' löschen, bedeutet dies, dass der "innerhalb" der Funktionsdefinition zugewiesene Speicher func2()ebenfalls gelöscht wurde?
Aquarius_Girl
2
@ Anisha Kaul: Ja. Der Speicher wurde innen zugewiesen func2()und in der nächsten Zeile außen freigegeben. Es ist jedoch eine ziemlich fehleranfällige Methode, mit Speicher umzugehen, wie ich bereits sagte, dass Sie stattdessen eine Variante von RAII verwenden würden. Übrigens, Sie hören sich an, als würden Sie C ++ lernen. Ich empfehle, ein gutes C ++ - Einführungsbuch zu lesen, aus dem Sie lernen können. Wenn Sie eine Frage haben, können Sie die Frage zum späteren Nachschlagen jederzeit im Stapelüberlauf veröffentlichen. Kommentare sind nicht dazu gedacht, völlig neue Fragen zu stellen.
In silico
Jetzt habe ich verstanden, du hast es richtig gemacht! Die Funktion hat einen Zeiger zurückgegeben, und außerhalb dieser Funktion haben Sie den Speicher gelöscht, auf den sie zeigte. Es ist jetzt klar und danke für den Link.
Aquarius_Girl
und du hast die Antwort bearbeitet ?? : mad: Ich hätte es leicht übersehen können. ;);)
Aquarius_Girl
@ Anisha Kaul: Nein, habe ich nicht. Das letzte Mal, dass ich meine Antwort bearbeitet habe, war am 10. Januar, gemäß dem Zeitstempel unter meinem Beitrag.
In Silico
18

Eine lokale Variable ist der Speicher auf dem Stapel. Dieser Speicher wird nicht automatisch ungültig, wenn Sie den Gültigkeitsbereich verlassen. Von einer tiefer verschachtelten Funktion (höher auf dem Stapel im Speicher) ist der Zugriff auf diesen Speicher absolut sicher.

Sobald die Funktion zurückkehrt und endet, werden die Dinge gefährlich. Normalerweise wird der Speicher bei Ihrer Rückkehr nicht gelöscht oder überschrieben, was bedeutet, dass der Speicher an diesen Adressen noch Ihre Daten enthält - der Zeiger scheint gültig zu sein.

Bis eine andere Funktion den Stapel aufbaut und überschreibt. Aus diesem Grund kann dies für eine Weile funktionieren - und dann plötzlich nicht mehr funktionieren, nachdem ein besonders tief verschachtelter Satz von Funktionen oder eine Funktion mit wirklich großen oder vielen lokalen Objekten diesen Stapelspeicher wieder erreicht hat.

Es kann sogar vorkommen, dass Sie denselben Programmteil wieder erreichen und Ihre alte lokale Funktionsvariable mit der neuen Funktionsvariablen überschreiben. All dies ist sehr gefährlich und sollte stark entmutigt werden. Verwenden Sie keine Zeiger auf lokale Objekte!

Pica
quelle
2

Eine gute Sache, an die Sie sich erinnern sollten, sind diese einfachen Regeln, die sowohl für Parameter als auch für Rückgabetypen gelten ...

  • Wert - erstellt eine Kopie des betreffenden Elements.
  • Zeiger - bezieht sich auf die Adresse des betreffenden Artikels.
  • Referenz - ist buchstäblich das fragliche Element.

Für jeden gibt es eine Zeit und einen Ort. Lernen Sie sie also unbedingt kennen. Lokale Variablen sind, wie Sie hier gezeigt haben, genau das, begrenzt auf die Zeit, in der sie im Funktionsumfang lokal aktiv sind. In Ihrem Beispiel wäre es gleichermaßen falsch gewesen , einen Rückgabetyp von int*und eine Rückgabe &izu haben. In diesem Fall wären Sie besser dran ...

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

Dies würde den Wert Ihres übergebenen Parameters direkt ändern. Während dieser Code ...

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

würde nicht. Es würde nur den Wert von oValuelocal in den Funktionsaufruf ändern . Der Grund dafür ist, dass Sie tatsächlich nur eine "lokale" Kopie von oValueund nicht sich oValueselbst ändern würden .

David Sumich
quelle