2

The question is not why and when one should use it. I'm wondering what exactly happens in the inferior level, because we get really curious results doing it.

For example, it is clear when you use it like this:

int& func(int& num) {
    ++num;
    return num;
}

Here we pass a variable by reference, change it, and return a new reference to the same variable.

But what happens if we do this? :

int& func(int num) {
    ++num;
    return num;
}

The variable num ceases to exist when the function func is done. C++ does not allow to create references without initializing them with a variable. But in this case, both compilation and execution go without errors. For example, if we don't initialize an int, we get random behavior:

int num;
std::cout << num; // gives 6422352 or sth like that

But if we initialize it with the function above:

int num1(1);
int num2(func(num1));
cout << num2; // console freezes for a second or two, and execution stops, without any errors

So I've been wondering:

  1. Why are we not getting a compilation error returning a reference to a non existent object?
  2. What is this that is assigned to num2?
  3. Why does it not cause any execution errors, but just stops the program?
S. Ellis
  • 113
  • 1
  • 9
  • 2
    You're in [for a treat](https://stackoverflow.com/a/6445794/11683). – GSerg Aug 22 '20 at 11:45
  • @GSerg, not relevant. I am not asking about pointers, I know that variable lives while there are active pointers to it. Here the variable `num` dies when we're out of the function, but we can still return a **reference** to it without any errors. – S. Ellis Aug 22 '20 at 11:51
  • 1
    It's not illegal to return a reference to a non-existant object, It's just not OK to then access that reference. That's why it not a compilation error. In general C++ has the philosophy that it's up to you to avoid errors, not upto the compiler to catch your errors. This helps C++ compilers generate efficient code. If you do something wrong most of the time all C++ says is that the error causes *undefined behaviour*. Beginners often have trouble with this concept but it means nothing more and nothing less than exactly what it says. The behaviour of your program is undefined. – john Aug 22 '20 at 11:51
  • 3
    @S.Ellis The same 'hotel room' argument can be made for references, there's no difference in this regard. – john Aug 22 '20 at 11:53
  • The second code snippet causes undefined behaviour since it returns a reference to a variable that ceases to exist when the function returns. (An argument passed by value is the same as a variable of automatic storage duration - it ceases to exist when the function returns). Being undefined behaviour, the standard requires no diagnostic. But most modern compilers can be configured to issue warnings in such cases. – Peter Aug 22 '20 at 11:53
  • @john the difference is that we can create and manage a **pointer** without an object, but we can't do so with a **reference**. If there is no object, I don't understand what exactly are we returning. – S. Ellis Aug 22 '20 at 11:56
  • 4
    @S.Ellis If it helps, a compiler would typcally implement a reference as a machine address (just like a pointer). So what's being returned is the address that the variable formerly occupied. – john Aug 22 '20 at 11:57
  • 1
    You can create dangling references just like you can create dangling pointers. It's not a syntax error, just a terrible bug. – Blastfurnace Aug 22 '20 at 11:59
  • @Blastfurnace but references are not objects, they don't exist in memory, they are just another names for variables. At least that's what the textbooks say. So, how can they be dangling? Looks like i'm missing some fundamental concepts here. – S. Ellis Aug 22 '20 at 12:04
  • 3
    References absolutely do take up memory, they might be optimised away to the original variable in some cases. You can basically think of a reference as a non-null pointer – Alan Birtles Aug 22 '20 at 12:10
  • 2
    It's useful to think of them as aliases when writing and reasoning about C++ code. When the compiled code runs on actual hardware they are typically implemented as pointers because an address is the "name" of an object. In this case the name of an object persists after the object is destroyed and it's a bug to use that reference (or pointer) as if the object is still alive. – Blastfurnace Aug 22 '20 at 12:10
  • 2
    A dangling reference is always possible, if the reference lives longer than the referred to object. That's true however you define reference. I guess you are thinking that the fact that an object is being referred to should somehow keep it alive. That is not true in general in C++. – john Aug 22 '20 at 12:13
  • ... *random behavior* ... In C++ terminology, it is called **undefined behavior**. You might get an apparently random-ish value, you might get [time travel](https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633), you might get a crash, or worse: it may appear to work correctly as you'd be expecting so you don't notice the mistake. – Eljay Aug 22 '20 at 12:36

2 Answers2

4

There is nothing wrong with returning a reference to a local variable from a function. However using that reference results in undefined behaviour.

Hi - I love SO
  • 615
  • 3
  • 14
  • 4
    One can argue that returning something you are not able to use in a save way is wrong. – Unlikus Aug 22 '20 at 12:13
  • 3
    @Unlikus You can argue that, sure. But there's a big difference between *returning* that dangling reference and actually trying to *use* it. Just returning it is valid, but *using* it results in [UB](https://en.cppreference.com/w/cpp/language/ub) and renders your *entire* program invalid. – Jesper Juhl Aug 22 '20 at 12:34
1

Well, I compiled your code with g++ version 5.4.0.

So I've been wondering:

  1. Why are we not getting a compilation error returning a reference to a non existent object?

The compiler doesn't give an error, but it does give you a warning.

sampe.cpp: In function ‘int& func(int)’:
sampe.cpp:6:15: warning: reference to local variable ‘num’ returned [-Wreturn-local-addr]
 int& func(int num)
               ^

Also, you are not returning a reference to a non-existent object. When you are returning the reference, the object (or rather int) is present and not out-of-scope. Once the return statement is executed, then it goes out-of-scope.

That's why the compiler compiles the code and just gives a warning.

  1. What is this that is assigned to num2?
  1. Why does it not cause any execution errors, but just stops the program?

Now, when I execute the program, I get Segmentation fault, every single time (without any output). A quick use of gdb tells me that the line

int num2(func(num1));

in main() is the culprit. It's where my program crashes.

====================================================

TLDR;

As stated in comments to your question and the answer by @Hi - love SO, it's clear that there's nothing wrong with returning a reference to a local variable.

May be you are just using an old compiler which is somehow able to give you output as your program is entering the Undefined behaviour territory.

linuxartisan
  • 2,396
  • 3
  • 21
  • 40