Returning a reference to a local variable in C++
Asked Answered
K

3

149

Is the following code (func1()) correct if it has to return i? I remember reading somewhere that there is a problem when returning a reference to a local variable. How is it different from func2()?

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

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}
Kiersten answered 10/1, 2011 at 4:36 Comment(3)
If you change func1() to use dynamically allocated memory then they are the same :-) int& i = * new int;Sprawl
Related for const locals: #2784762Aras
You could do int&& func1() { return 1; }Estelleesten
E
232

This code snippet:

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

will not work because you're returning an alias (a reference) to an object with a lifetime limited to the scope of the function call. That means once func1() returns, int i dies, making the reference returned from the function worthless because it now refers to an object that doesn't exist.

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

The second version does work because the variable is allocated on the free store, which is not bound to the lifetime of the function call. However, you are responsible for deleteing the allocated 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
}

Typically you would wrap the pointer in some RAII class and/or a factory function so you don't have to delete it yourself.

In either case, you can just return the value itself (although I realize the example you provided was probably contrived):

int func3()
{
    return 1;
}

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

Note that it's perfectly fine to return big objects the same way func3() returns primitive values because just about every compiler nowadays implements some form of return value optimization:

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

Interestingly, binding a temporary to a const reference is perfectly legal 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();  
}
Endanger answered 10/1, 2011 at 4:38 Comment(8)
Beautiful explanation. :hattip: In the third code snippet, you are deleting int* p = func2(); delete p; Now, when you deleted 'p', does it mean that the memory allocated "inside" the function func2() 's definition also got deleted?Riga
@Anisha Kaul: Yes. The memory was allocated inside func2() and released outside in the next line. It is a rather error-prone way to handle memory though, like I said you would use some variant of RAII instead. By the way, you sound like you're learning C++. I recommend picking up a good introductory C++ book to learn from. Also, for future reference if you have a question, you can always post the question on Stack Overflow. Comments are not meant for asking totally new questions.Endanger
Now I understood, you've done it right! The function was returning a pointer, and outside that function, you have deleted the memory to which it was pointing to. It is clear now, and thanks for the link.Riga
and you have edited the answer?? :mad: I could have missed it easily. ;) ;)Riga
@Anisha Kaul: No, I didn't. The last time I edited my answer was on January 10th, according to the time stamp under my post.Endanger
Oh Ho! By 'answer' I was referring to your 'comment', that was an answer for me. Your edit was: Also, for future reference if you have a question, you can always post the question on Stack Overflow. Comments are not meant for asking totally new questions this was helpful, I used to be frightened ;) to create new similar questions. and BTW, you can simply write @Anisha, instead of the full name.Riga
I was interested in finding out if func1 would compile, and just silently fail, or if there was a warning or something. After some experimentation, I found that g++ throws a warning at compile-time, but still compiles, and throws a segmentation fault at run-time.Smashed
@Smashed for me, it does not throw a segmentation fault at run-time. It compiles & run perfectly. The only thing that I notice is that if I store what func1() returns in a variable, say a, that variable would nonetheless hold the value returned to it; no error in my case. Also the difference between the address of the local variable (the one in the function) and the address of a is 24, which is exactly 3 bytes. It does not produce any error in my case. Is this answer outdated? Also, why 3 bytes?Forsook
B
24

A local variable is memory on the stack, and that memory is not automatically invalidated when you go out of scope. From a function deeper nested (higher on the stack in memory), it’s perfectly safe to access this memory.

Once the function returns and ends though, things get dangerous. Usually the memory is not deleted or overwritten when you return, meaning the memory at that address is still containing your data - the pointer seems valid.

Until another function builds up the stack and overwrites it. This is why this can work for a while - and then suddenly cease to function after one particularly deeply nested set of functions, or a function with really huge sized or many local objects, reaches that stack-memory again.

It even can happen that you reach the same program part again, and overwrite your old local function variable with the new function variable. All this is very dangerous and should be heavily discouraged.

Do not use pointers to local objects!

Baelbeer answered 16/6, 2015 at 10:6 Comment(0)
S
2

A good thing to remember are these simple rules, and they apply to both parameters and return types...

  • Value - makes a copy of the item in question.
  • Pointer - refers to the address of the item in question.
  • Reference - is literally the item in question.

There is a time and place for each, so make sure you get to know them. Local variables, as you've shown here, are just that, limited to the time they are locally alive in the function scope. In your example having a return type of int* and returning &i would have been equally incorrect. You would be better off in that case doing this...

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

Doing so would directly change the value of your passed in parameter. Whereas this code...

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

would not. It would just change the value of oValue local to the function call. The reason for this is because you'd actually be changing just a "local" copy of oValue, and not oValue itself.

Sikko answered 26/4, 2016 at 5:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.