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?
CodePudding user response:
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 .
CodePudding user response:
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.
CodePudding user response:
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 as 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...