0

I don't understand how this code works:

class AAA {
public:
    short a, b;
};

AAA &getRef() {
    AAA aaa = {2, 6};
    return aaa;
} // 'aaa' is destroyed here right?

int main() {
    AAA &ref = getRef();
    cout << ref.a << ", " << ref.b << endl;

    cin.ignore();
    return 0;
}

Shouldn't there be an error trying to access ref.a and ref.b? When I use pointers, I don't get an error either. I mean,this prints "2, 6" every single time.

EDIT: Is it because the memory is still set to those numbers?

Ry-
  • 218,210
  • 55
  • 464
  • 476
Susan Yanders
  • 854
  • 1
  • 9
  • 18
  • 12
    It's undefined behaviour, not just an error. – chris Sep 16 '13 at 22:25
  • @chris Except for the fact that I keep getting the same results – Susan Yanders Sep 16 '13 at 22:26
  • 7
    @Susan So what? It's still undef behaviour. It might blow up next time. Or worse, once you write code that is deployed on your company's customers' systems. – us2012 Sep 16 '13 at 22:27
  • 4
    @SusanYanders: Getting the same results is one possible behavior when you have undefined behavior. – Bill Sep 16 '13 at 22:27
  • @us2012 The code is obviously working when it shouldn't! – Susan Yanders Sep 16 '13 at 22:27
  • @Susan Who says it shouldn't?! It's called undefined behaviour because you can't know what will happen. All you know is that it's possible that it blows up. And that's why you shouldn't do it. – us2012 Sep 16 '13 at 22:28
  • There is a related thread with a nice disassembly and explanation over here: http://www.cplusplus.com/forum/general/13808/ – Jens Sep 16 '13 at 22:29
  • Stack-allocated objects are destroyed at the end of their respective scopes. Trying to access them afterwards is undefined behavior. Dynamically-allocated objects are on the heap and won't be destroyed until the user calls `delete`. – David G Sep 16 '13 at 22:29
  • Try calling a second method (with lots of local variables, all initialized to zero) between that first method and the output. You're likely (but strictly, not guaranteed) to find that suddenly you don't get those results. The reason for this undefined behaviour isn't some kind of arbitrary randomness to annoy you, it results from the way compilers use the processor stack. There may be a return-value optimisation issue in this case - I always forget the rules for that. –  Sep 16 '13 at 22:33
  • 3
    Please don't remove the content of your post because of the answers. – David G Sep 16 '13 at 22:34
  • If you looked at the compiler output (asm), I suspect it's copying `aaa` into `ref` when `getRef` returns. (don't count on the compiler always doing that.) – Ricky Sep 16 '13 at 22:34
  • 1
    @RickyBeam I think you have some misconceptions about how references work. – us2012 Sep 16 '13 at 22:35
  • @SusanYanders If you want to understand what might actually be going on here, you need to understand how the stack works. Possible keywords that will lead you to relevant tutorials: stack, stack frame, calling convention. This is a worthwhile exercise, but don't get into the habit of trying to explain instances of undefined behaviour - it's usually not productive to do so. – us2012 Sep 16 '13 at 22:37
  • 1
    This question has been asked hundreds of times here. **Undefined behaviour does not mean you get an error!** See ["Somebody told me that in basketball you can't hold the ball and run. I got a basketball and tried it and it worked just fine. He obviously didn't understand basketball."](http://c-faq.com/ansi/experiment.html) and other links at http://stackoverflow.com/tags/undefined-behavior/info – Jonathan Wakely Sep 16 '13 at 22:39
  • possible duplicate of [Can a local variable's memory be accessed outside its scope?](http://stackoverflow.com/questions/6441218/can-a-local-variables-memory-be-accessed-outside-its-scope) – Jonathan Wakely Sep 16 '13 at 22:46
  • @us2012, perhaps, but a) he's returning an object not a ref to `aaa`, and b) this is uncharted compiler waters. (looking at what g++ 4.4.5 generates, it's even simpler... `aaa` never exists, `ref` from `main` is populated directly. and btw, it emits a warning.) – Ricky Sep 16 '13 at 23:01
  • 1
    @RickyBeam Your point (a) isn't correct, this function *does* return a reference. – us2012 Sep 16 '13 at 23:03
  • @us2012, please take a minute to look at your compiler output and tell me if it's returning anything or simply filling in `ref` on the stack. – Ricky Sep 16 '13 at 23:12
  • @Ricky [Godbolt sample](http://gcc.godbolt.org/#%7B%22version%22%3A3%2C%22filterAsm%22%3A%7B%22labels%22%3Atrue%2C%22directives%22%3Atrue%2C%22commentOnly%22%3Atrue%2C%22intel%22%3Atrue%2C%22colouriseAsm%22%3Atrue%7D%2C%22compilers%22%3A%5B%7B%22source%22%3A%22class%20A%7B%5Cnpublic%3A%5Cn%20%20%20%20int%20a%2C%20b%3B%5Cn%7D%3B%5Cn%5CnA%20%26g()%20%7BA%20a%20%3D%20%7B2%2C%206%7D%3B%20return%20a%3B%7D%20%5Cn%5Cnint%20main()%20%7B%5Cn%20%20%20%20A%20%26r%20%3D%20g()%3B%5Cn%20%20%20%20return%200%3B%5Cn%7D%22%2C%22compiler%22%3A%22%2Fusr%2Fbin%2Fg%2B%2B-4.8%22%2C%22options%22%3A%22-O0%22%7D%5D%7D) – us2012 Sep 16 '13 at 23:22

4 Answers4

7

It "works" because the memory for aaa hasn't been overwritten when the function returns. If you modify the AAA class to have a destructor that modifies a and b, or if you use some code that writes to the stack, it will almost certainly overwrite the values.

Returning a reference to a local variable is defined by the C++ standard as a "undefined behaviour". The standard often does this in cases where it may, for example, be difficult to determine that the value is indeed stack-based.

For example, consider:

class BBB
{
   AAA& x;
  public:
   BBB(AAA& a) : x(a) {}
   AAA& getX() { return x; }
};

AAA& getReg()
{
   AAA aaa = { 2, 6}
   BBB bbb(aaa);
   return bbb.getX();
}

Most modern compilers will issue a warning for the scenario you have, and some may also give a warning for the code I just wrote. But it's almost certainly possible to come up with some more convoluted case for where it's NOT possible to diagnose this "error".

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
5

Unfortunately this is illegal code that invokes undefined behaviour, but it is not flagged as error by the compiler (although it might if more compiler diagnostics are enabled).

It happens to work by accident, presumably because the memory location where the object is created is not re-used for anything else, and is not cleared or overwritten, so you are just "lucky" that the values, once put there, remain.

Seg Fault
  • 1,331
  • 8
  • 16
2

It seems you want "proof" that it can fail. Maybe try:

int main() {
    AAA &ref = getRef();
    cout << "Hello, world!" << endl;
    cout << ref.a << ", " << ref.b << endl;
}

also, you should enable and pay attention to your compiler's warnings.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
1

Replace the shorts with a user-defined type with a complicated destructor (like string), and you will probably see this crash. Because the memory has been reclaimed, but the old value is still sitting there, you can (sometimes) see the value of built-in types (like int, short, etc.) even after destruction.

[Sample Code]

Bill
  • 14,257
  • 4
  • 43
  • 55
  • You can sometimes see the value of non-built-in types too. There's no reason it has to crash just because of a user-defined destructor. – Benjamin Lindley Sep 16 '13 at 22:39
  • In my experience, you're a lot more likely to crash dereferencing deleted pointers than looking at reclaimed stack memory. Since it's undefined, your experiences almost certainly differ. :D – Bill Sep 16 '13 at 22:50