Is the following UB (undefined-behavior) ?
Is it really one level "too much" that value is now dangling and there is no lifetime extension done by the compiler/language rules?
const int &get_value(const int &value) { return value; };
int main()
{
const auto &value = get_value(5);
printf("Value is: %d", value);
}
Yes, this is UB. When passing by 5 to get_value(), a temporary object is created and function parameter const reference value bind to it. Since, the temporary object bound to function parameter value, it will persist until the completion of the full expression containing the call. In the main(), you are dereferencing a reference which is not bound to a living object and this is undefined bahavior.
Related
I'm trying to get a better grasp of how lvalues and rvalues are dealt with as references, so I created this toy example:
#include <iostream>
struct Val
{
Val(int num) : num(num){};
~Val()
{
std::cout << "Destructing with value " << num << std::endl;
}
int num;
};
const Val &test(const Val &val)
{
return val;
}
int main()
{
std::cout<< "Creating foo with value 5" <<std::endl;
const Val &foo = test(Val(5));
std::cout<< "Creating bar with value 3" <<std::endl;
const Val &bar(3);
std::cout<< "Finishing main function" <<std::endl;
return 0;
}
This prints out:
Creating foo with value 5
Destructing with value 5
Creating bar with value 3
Finishing main function
Destructing with value 3
Essentially we're seeing this rvalue Val(5) bind to const reference parameter val in function test, and that same value returned — however, the destructor gets called immediately as it's a temporary. But when we try constructing Val(3) and assigning to a const reference, it remains in scope for the entire block.
I was under the conception that we can bind rvalues to const references and that'll extend their lifetime until that reference goes out of scope, but that seems to not necessarily be the case here. I'd appreciate any insight into where I'm misunderstanding.
Given const Val &foo = test(Val(5));, the temporary Val(5) will be destroyed after the full expression immediately, its lifetime won't be extended to the lifteime of the reference foo. It's not bound to foo directly, but bound to the reference parameter of test.
In reference initialization,
(emphasis mine)
Whenever a reference is bound to a temporary or to a subobject
thereof, the lifetime of the temporary is extended to match the
lifetime of the reference, with the following exceptions:
a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if
the function returns a reference, which outlives the full expression,
it becomes a dangling reference.
In general, the lifetime of a temporary cannot be further extended by
"passing it on": a second reference, initialized from the reference to
which the temporary was bound, does not affect its lifetime.
When you add an output to test() itself
const Val &test(const Val &val)
{
std::cout << "test with value " << val.num << '\n';
return val;
}
You will see, that the temporary lives until the end of the function, but not beyond
Creating foo with value 5
test with value 5
Destructing with value 5
Creating bar with value 3
Finishing main function
Destructing with value 3
I was under the conception that we can bind rvalues to const references and that'll extend their lifetime until that reference goes out of scope
Yes, indeed val parameter does extend the lifetime of Val(5), but when test returns, val itself is destroyed, and nothing keeps Val(5) alive anymore, so it is destroyed too.
And by returning by reference you are actually returning a dangling reference, so you have undefined behavior: the fact that the parameter val is a reference to the actual argument Val(5) does not influence the fact that val is no more available after test returns, and you are returning (well, trying to return) it (val) by reference, not its referenced entity.
Consider the following code -
#include <iostream>
#include <stdio.h>
const int & retRef() {
return 6;
}
int main()
{
const int& k = retRef();
printf("Value: %d\n", k);
printf("Address: %p\n", &k);
printf("Value: %d\n", k);
return 0;
}
The output is -
Value: 6
Address: 0x7ffd45bf544c
Value: 32692
Why the value changed after printing the address of the variable k? If I replace the line const int& k = retRef() with const int& k = 6;, the output is as expected.
Why is this different behavior? Thanks in advance
Your code has undefined behavior; You're trying to bind a temporary int (which is constructed from literal 6) to reference as return value, but the temporary will be destroyed immediately then retRef() always returns a dangled reference, and k is dangled too. Any dereference on it leads to UB.
Whenever a reference is bound to a temporary or to a subobject thereof, the lifetime of the temporary is extended to match the lifetime of the reference, with the following exceptions:
a temporary bound to a return value of a function in a return statement is not extended: it is destroyed immediately at the end of the return expression. Such function always returns a dangling reference.
On the other hand, const int& k = 6; works fine because the lifetime of temporary is extended to the lifetime of k as stated above.
You are returning a reference to a temporary object which will cause undefined behaviour. The object will not be available once function returns.
n4659 - § 15.2:
(6.2) — The lifetime of a temporary bound to the returned value in a function return statement (9.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
In function retRef you are returning a reference to a local variable.
The objects inside the function will be destroyed when exiting the function and all references to it will become invalid. Using of that link will further entail undefined behavior...
const int & retRef()
{
return 6;
}
I'm wondering why returning const reference of a local object is illegal whereas returning a local object is legal as long as you assign it to a const reference?
vector<int> f_legal() {
vector<int> tempVec;
tempVec.push_back(1);
return tempVec;
}
const vector<int>& f_illegal() {
vector<int> tempVec;
tempVec.push_back(1);
return tempVec;
}
void g() {
const vector<int>& v1 = f_legal(); // legal
const vector<int>& v2 = f_illegal(); // illegal
}
Edit:
My point is that if assigning a const ref to a returned local variable is legal, then shouldn't assigning a const ref to a returned const ref of a local variable be legal as well?
Returning a reference to a local variable is illegal (Undefined Behavior). Period. There is no const or mutable clause.
This is because local function variables have automatic storage duration. They are "destroyed" after the function exits. If the function returns a reference to such a variable, that reference it is said to be dangling: it refers to an object that no longer exists.
The first one is legal because of a special C++ rule: initializing a reference to a prvalue extends the lifetime of that temporary object to the lifetime of the reference.
Even if you assign it to a const reference, the return value is declared as passed by value, that means it'll be copied[1] to outside as a temporary object, and then binded to the const reference. Binding temporary object to a const reference is fine, the object won't be destroyed until getting out of the lifetime of the const reference.
On the other hand, returning reference of a local variable is illegel. The local variable'll be destroyed when the function returned, that means the outside reference will be dangled.
EDIT
My point is that if assigning a const ref to a returned local variable is legal, then shouldn't assigning a const ref to a returned const ref of a local variable be legal as well?
The point is the 1st case is not assigning a const ref to a returned local variable, it's assigning a const ref to a returned temporary variable. (Which might copied from the local variable.)
[1] The copy might be omitted according to RVO technically.
Most likely because it would totally ruin the whole stack based calling conventions that have served us well for decades...that pretty much every CPU assumes.
Why these definitions are all ok:
int func(int p=255) {
return p;
}
int func1(const int &p=255) {
return p;
}
but this definition:
int func2(int &p=255) {
return p;
}
leads to compile error ?
What is the logic behind it ?
Taking arguments by reference means, you dont work with your local copy of the variable, but with a variable already defined in the scope of the calling function.
While your first example makes sense (you have a local variable p that you can fill with a default value) the second example is a bit more tricky: Usually when using references you expect the variable to have an address, since you want to modify it. For const-refernces, the compiler will still allow you to pass a literal, even if something like "reference to a literal" makes no sense at all.
In the third case the compiler expects you to modify p. But what part of the memory should this modification affect? "255" has no address - therefore it cant be used as a reference.
If you want to have a more detailed explanation, you should probably look for keywords like "rvalue" and "lvalue".
The attempted function definition
auto func2( int& p = 255 )
-> int
{ return p; }
… fails because you can't bind an rvalue to a reference to non-const. Basically that rule is because a simple value like 255 isn't modifiable. While the reference can be used to modify.
One simple solution is to express the default as a separate overload:
auto func2( int& p )
-> int
{ return p; }
auto func2()
-> int
{
int scratchpad = 255;
return func2( scratchpad );
}
A non-const reference must be bound to lvalue (i.e. its address could be got). 255 (i.e. an int literal) is not a lvalue, so int &p=255 fails.
A const reference could be bound to rvalue, and for this case, a temporary int will be created and initialized from 255. The temporary int's lifetime will be the same as the const reference.
int func(int p=255) {
return p;
}
p here is copied by value, and it is defined to exist in the scope of func.
int func2(int &p) {
return p;
}
// e.g. use:
int value = 10;
func2(value); // func2 *could* modify value because it is passed by non-const reference
In this case the compiler here expects p to have a name somewhere in memory (i.e. lvalue), so it can possibly write to it within func2. Passing by non-const reference allows you to modify the variable used in the function call. Since p must belong to someone else somewhere since it can be modified, you can't assign a default value to it.
But what about the const-reference case? Here, the compiler is smart enough to know that p can never be written to since it is const, so it doesn't need to have a name in memory to write to. In cases of a literal being passed (e.g. 255), it (behind the scenes) essentially creates a temporary and passes that temporary variable to the function.
int func1(const int &p=255) {
return p;
}
func1(10);
// Behind the scenes, the compiler creates something along these lines
// since it can never be modified.
const int some_temporary = 10;
func1(some_temporary);
This is follow up of question Is there a sequence point between return and expression in return statement? . The answer-er isn't replying to my comments , neither I'm unable to understand from his answer, nor I know how to bump the thread. So I created this question, my sincere apologies for doing this.
Consider the below code :
#include <iostream>
using namespace std;
struct foo{const char* bar ; foo(): bar("This is foo"){} };
foo returnByValue(){ return foo(); }
const foo& returnByConstRef() { return returnByValue(); } // *
int main() {
std::cout<< returnByConstRef().bar <<std::endl; // is life of temp is extended in while this expression?
return 0;
}
My understanding is that returnByValue() expression (inside returnByConstRef()) is the copy of temporary object foo() (using copy ctor) . Now returnByConstRef() which is const reference to temp object returned by returnByValue() (copy of original temp() object in the code) , Now when I invoke returnByConstRef().bar why is it undefined behavior ?
Where is my thinking wrong? , does RVO does this?
This isn't due to RVO, the standard specifies that temporaries bound to returned values do not have their lifetime extended:
N3337 [class.temporary]/5: [...] The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:
[...]
The lifetime of a temporary bound to the returned value in a function return statement (6.6.3) is not
extended; the temporary is destroyed at the end of the full-expression in the return statement.
[...]
For your example:
const foo& returnByConstRef()
{
return returnByValue();
}
returnByValue() is a temporary which is being bound to the return value, kind of like this:
const foo& returnByConstRef()
{
const foo& _temporary_object = returnByValue();
return _temporary_object;
//object referenced by _temporary_object is destroyed
}
Such temporaries do not have their lifetime extended; they are destroyed on function exit. Maybe it would help you to think of it like this:
function returnByConstRef
call returnByValue, put value on the stack
put pointer to value in return register
clear up stack, invalidating pointer to the value