0

I was wondering about the legitimate of returning a local variable from a function (a variable that was made in the function) as a reference. My logic tells me that this should be impossible and cause a compiler error. However, my compiler thinks different - it compiles and print the value 10! (VS 2017) If i try to compile it on an online compiler it throws a segmentation fault.

Can anyone shed some light on it? If you were asked what would this piece of code do, would you say it prints just fine or a compiler error?

Here's the code:

    #include <iostream>
    using namespace std;


    int &func(int x, int y) {
    int tmp = x > y ? x : y;
    return tmp;
    }

    void main() {
    int a = 3, b = 10;
    int &t = func(a, b);
    cout << t << endl;

}

sepp2k
  • 363,768
  • 54
  • 674
  • 675
Sonya gold
  • 87
  • 1
  • 6
  • 1
    A compiler is not required to diagnose undefined behaviour. In this simple case it will probably do if you enable warnings, though. – Quentin May 07 '20 at 09:10
  • [Can a local variable's memory be accessed outside its scope?](https://stackoverflow.com/questions/6441218/can-a-local-variables-memory-be-accessed-outside-its-scope/6445794#6445794) – molbdnilo May 07 '20 at 09:11
  • There is no problem in returning a reference to an automatic object so long as you never use that reference. That's why it's not an error. However, a decent compiler will warn you about this, and it's decent to treat warnings as errors. – molbdnilo May 07 '20 at 09:13
  • Some interesting comments in https://en.cppreference.com/w/cpp/algorithm/max – Bathsheba May 07 '20 at 09:13
  • @molbdnilo am I using it in this example? since i'm printing t. – Sonya gold May 07 '20 at 09:23
  • How could you possibly print the value without using the value? – molbdnilo May 07 '20 at 09:25

2 Answers2

2

Undefined behaviour is essentially defined by not resulting in an easy diagnosable error. Read into https://en.cppreference.com/w/cpp/language/ub. An advanced compiler might be able to detect one, but is not required to.
This is the true problem of UB - it actually can do exactly what you want it to do. Until it doesn't, resulting in bugs that do not trigger at first, making them potentially hard to fix when they finally do.

What a compiler might do with your piece of code is this:

  • The memory for tmp is reserved within the stack.
  • The reference is created. Let's for simplicity act as if it was a regular pointer. Essentially, the address of that memory is used.
  • The reference is returned.
  • As the function is left, everything in the stack memory belonging to the function is deallocated. This means that those memory addresses are no longer reserved, and another process might reserve it instead.
  • However: there is no need to spend any time doing anything with the content of the memory at that position. It still contains a ten in integer format.
  • Your console print accesses the memory at the address. While it is not reserved, it still contains the value. As your variable t is declared as integer, it reads that part of the memory as one, and the ten is still there, everything working out fine.

As that part of the memory is not reserved after the return, something else might however reserve it and write in it, before the console print happens. If so, the output might be very well be something like -36234646346.

Now, in all of this, I assumed a lot of things about how the compiler works. This is not given. Your question
"If you were asked what would this piece of code do, would you say it prints just fine or a compiler error?" does not have a definite answer. One can say that it might do what you want, but that is not a given.
A common joke is that, since a compiler is allowed to do anything if the code has undefined behaviour, it could also result in a swarm of demons coming out of your nose.

That is of course exaggerated, but the things is, undefined behaviour can do a lot of things you really don't expect.
For example: look at this: http://www.cpp.sh/32x7f. In there, a two-dimensional array is accessed out of bounds in a console output. What happens when you run it is that it keeps on iterating, printing out the iterator variable at over a hundred while the for loop should have stopped after three. I have no idea what happens there, the out-of-bounds access should not be able to mess with the loop, but apparently it does. (Note that the global variable in there is bad style, but unrelated to the behaviour.)

Important thing is: learn to recognize undefined behaviour, and never rely on it working in a certain way.

Aziuth
  • 3,652
  • 3
  • 18
  • 36
0

You never return a variable (technically an l-value or location) in C++. You return a value, or a location (a reference is a location, mentally a pointer-like thing).

Your confusing func returns a location to a temporary thing. It probably involves some undefined behavior.

Practically speaking, enable warnings in your compiler. With a recent GCC, compile with g++ -Wall -Wextra -g

The C++11 specification (read n3337) mostly specifies the behavior of correct C++ programs. On undefined behavior, a compiler (or C++ implementation) might trigger a nuclear war and still stay compliant with that specification.

In practice, compiler developers are aware of Rice's theorem but try to give, in some cases, useful warnings to users. You probably want to enable them (of course, compilation time increases in such case). You may want to use the Clang static analyzer or similar tools, maybe Frama-C or, at end of 2020, my Bismon thing coupled to GCC.

You could extend a recent GCC with your own plugin providing addition warnings.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547