Where a const reference refers to?

88 views Asked by At

There is a simple program

#include <stdio.h>

int counter = 3;

const int& f() {
    return counter++;
}

const int& g() {
    return counter++;
}

const int& h(int w) {
    counter = w;
    return counter;
}

void main()
{
    const int& x = f();
    const int& y = g();
    const int& z = g();
    const int& t = h(123);
    counter = 45678;
    printf("%d %d %d %d", x, y, z, t);
}

Why its output is 5 5 5 45678? Especially, why x and y are 5? If they are still connected to the counter after initialization, why they are not 45679 or something like?

3

There are 3 answers

0
Aconcagua On

Just as an addition to the answers given so far: Why undefined behaviour? Let's consider a hypothetical implementation of operator++:

template <typename T>
T& operator++(T& t) // pre-increment:
{
    t = t + 1;
    return t;
}
// should be clear so far...

template <typename T>
T operator++(T& t, int) // post-increment:
{
    // t = t + 1; // cannot do now, how to return the old value then???
    T b = t;      // need to store the value in a backup copy first!
    t = t + 1;    // NOW we can
    return t;     // need to return the OLD value -> cannot return by reference either,
                  // it's a TEMPORARY ...
}

As you now use operator++, which itself returns a temporary, you return a reference to temporary that doesn't exist any more after after returning – undefined behaviour.

Why do you now see 5? Pure technically (still UB!): As f and g look identical they both use identical offsets to the stack's end on being called and as being called immediately one after another they both start at the same stack end – so they will store the temporary at the same stack address, which is where the reference gets bound to. Note that this finally contains the value before last increment, thus 5, not 6. h does not need any additional values on the stack, thus the value there won't get overwritten – just alike when doing the assignment, so the value is yet retained. You might have seen something totally different if for some reason h did use the stack itself...

0
Caleth On

Your program has undefined behaviour. The return values of f and g cease to exist at the end of the statements that call them, so x, y and z don't refer to anything after their declaration.

1
Kaldrr On

Because of post-incrementation that f and g do.

const int& f() {
    return counter++;
}

const int& g() {
    return counter++;
}

counter++ doesn't actually return the counter, but a temporary int object returned by operator++(int). In C++, opreator++ has two forms.

Type& operator++(); //pre-increment
Type operator++(int); //post-increment

The int argument in the 2nd version is a dummy used to distinguish the calls, as you can't overload on return type alone As you can see the post-increment returns by value, not reference, so you're returning a reference to a temporary variable!

You can see warning from GCC and Clang if you turn them on. https://godbolt.org/z/f1fMP463a

So overall you invoke UB as the references you return are dangling. You ran into the worst case where it just seems to work.

Also, main must return int in C++.