The following code drops the warning C4172: returning address of local variable or temporary under Windows with MSVC. But im wondering is it a real error in this case or not? I know there is lots of similar topic here, and i have read lots of similar topics here from this warning. So in this case the returning value is a pointer from "main" function which should be alive until the end of program. If returningLocalPointer would return: "A something; return &something;" then yes it would be a problem, but in this case we return a pointer which exists until "main" ending. Or am i wrong?
class A
{
};
A* returningLocalPointer(A* a)
{
return a;
}
template<typename T>
T const& doWarning(T const& b)
{
A* c = returningLocalPointer(b);
return c; // error if uses call-by-value
}
int main()
{
A d;
auto m = doWarning(&d); //run-time ERROR
}
CodePudding user response:
Let's "instantiate" the function with T = A*
(as it does when you call it with doWarning(&d)
):
template
A* const& doWarning<A*>(A* const& b)
{
A* c = returningLocalPointer(&b);
return c;
}
You may be able to see where the problem is. c
is returned by reference, but it is a local variable that is immediately destroyed, thus doWarning
will always return a dangling reference.
MSVC seems to use the same warning for pointers-to-locals and references-to-locals, which is why it is talking about addresses when it's really about references. The GCC warning might be more clear:
In instantiation of 'const T& doWarning(const T&) [with T = A*]':
warning: reference to local variable 'c' returned [-Wreturn-local-addr]
return c; // error if uses call-by-value
^
note: declared here
A* c = returningLocalPointer(b);
^
CodePudding user response:
Yes, this is a real problem. Your program's behavior is undefined. c
is a different object from the pointer referenced by b
, and its lifetime ends at the end of doWarning
. Those two pointers point to the same A
object (d
), but that doesn't mean they're the same object.
To illustrate, I'll go more-or-less line-by-line and use diagrams:
A d;
auto m = doWarning(&d);
This creates an A
object named d
and passes an anonymous pointer to that object to doWarning
. I'll get to m
later, but for now the objects in play look like this:
d
┌─────┐ ┌─────┐
│ │ │ │
│ A* ├──────►│ A │
│ │ │ │
└─────┘ └─────┘
template<typename T>
T const& doWarning(T const& b)
{
Here, T
will be deduced to be A*
, since that's what got passed to it.
doWarning
accepts its parameter by reference, so the type of b
will be A* const &
. That is, b
is a reference to the anonymous pointer to d
from main
:
b d
┌───────────┐ ┌─────┐ ┌─────┐
│ │ │ │ │ │
│ A* const& ├──────►│ A* ├──────►│ A │
│ │ │ │ │ │
└───────────┘ └─────┘ └─────┘
A* c = returningLocalPointer(b);
Here you create another pointer, c
, that points to the same object as b
. I won't look at returningLocalPointer
, since it's more-or-less irrelevant. This line could be replaced with A* c = b;
and nothing would change. Your objects now look like this:
b d
┌───────────┐ ┌─────┐ ┌─────┐
│ │ │ │ │ │
│ A* const& ├──────►│ A* ├──────►│ A │
│ │ │ │ │ │
└───────────┘ └─────┘ └─────┘
▲
c │
┌─────┐ │
│ │ │
│ A* ├──────────┘
│ │
└─────┘
As you can see, c
is a different object than the object referenced by b
.
return c;
Since doWarning
returns an A* const&
(since T
is A*
), this initializes the return value to refer to the local variable c
:
b d
┌───────────┐ ┌─────┐ ┌─────┐
│ │ │ │ │ │
│ A* const& ├──────►│ A* ├──────►│ A │
│ │ │ │ │ │
└───────────┘ └─────┘ └─────┘
▲
return value c │
┌───────────┐ ┌─────┐ │
│ │ │ │ │
│ A* const& ├──────►│ A* ├──────────┘
│ │ │ │
└───────────┘ └─────┘
}
Now doWarning
ends, and so its local variable c
goes out of scope and its lifetime ends. That leaves doWarning
's return value dangling:
b d
┌───────────┐ ┌─────┐ ┌─────┐
│ │ │ │ │ │
│ A* const& ├──────►│ A* ├──────►│ A │
│ │ │ │ │ │
└───────────┘ └─────┘ └─────┘
return value
┌───────────┐
│ │
│ A* const& ├──────► Nothing here anymore
│ │
└───────────┘
auto m = doWarning(&d);
Now we get back to m
. auto
by itself will never deduce a reference type, so the type of m
is deduced to be A*
. That means the program will attempt to copy the pointer referenced by the reference that doWarning
returned. The pointer that doWarning
's return value referenced no longer exists though. Trying to copy a nonexistent object is an error, and if a program does so its behavior is undefined.
CodePudding user response:
You are returning a reference to the local variable c
so your code has undefined behaviour. You might get "lucky" and m
will happen to end up being a pointer to d
but there is no guarantee.
This code would have defined behaviour as it is returning a reference to d
rather than a reference to c
though it will still have undefined behaviour (and might therefore still produce a warning) if doWarning
is called with a temporary value:
A* returningLocalPointer(A* a)
{
return a;
}
template<typename T>
T const& doWarning(T const& b)
{
A* c = returningLocalPointer(&b);
return *c;
}
int main()
{
A d;
auto& m = doWarning(d);
// Undefined behaviour
auto& n = doWarning(A{});
}