提问人:Gergely Tóth 提问时间:12/27/2021 最后编辑:Alan BirtlesGergely Tóth 更新时间:12/27/2021 访问量:1320
这是一个真正的问题:警告 C4172:返回局部变量或临时地址
Is this a real problem: warning C4172: returning address of local variable or temporary
问:
以下代码删除警告 C4172:在具有 MSVC 的 Windows 下返回局部变量或临时地址。但我想知道在这种情况下这是否是一个真正的错误?我知道这里有很多类似的主题,我从这个警告中读到了很多类似的主题。因此,在这种情况下,返回值是来自“main”函数的指针,该指针应该一直处于活动状态,直到程序结束。如果 returningLocalPointer 将返回:“A something;return &something;“,那么是的,这将是一个问题,但在这种情况下,我们返回一个指针,该指针一直存在到”main“结尾。还是我错了?
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
}
答:
您将返回对局部变量的引用,因此您的代码具有未定义的行为。你可能会“幸运”,碰巧最终会成为指针,但不能保证。c
m
d
此代码将定义行为,因为它返回的是引用而不是引用,尽管如果使用临时值调用,它仍然具有未定义的行为(因此可能仍会产生警告):d
c
doWarning
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{});
}
评论
c
d
c
让我们用 “实例化” 函数(就像你用T = A*
doWarning(&d)
template
A* const& doWarning<A*>(A* const& b)
{
A* c = returningLocalPointer(&b);
return c;
}
您也许能够看到问题所在。 通过引用返回,但它是一个立即销毁的局部变量,因此将始终返回一个悬空引用。c
doWarning
MSVC 似乎对指向局部变量的指针和指向局部变量的引用使用相同的警告,这就是为什么它在真正涉及引用时谈论地址的原因。GCC 警告可能更明确:
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);
^
是的,这是一个真正的问题。程序的行为是未定义的。 是与 引用的指针不同的对象,其生存期在 的末尾结束。这两个指针指向同一个对象 (),但这并不意味着它们是同一个对象。c
b
doWarning
A
d
为了说明这一点,我将或多或少地逐行使用图表:
A d;
auto m = doWarning(&d);
这将创建一个名为的对象,并将指向该对象的匿名指针传递给 。我稍后会谈到,但现在游戏中的对象如下所示:A
d
doWarning
m
d
┌─────┐ ┌─────┐
│ │ │ │
│ A* ├──────►│ A │
│ │ │ │
└─────┘ └─────┘
template<typename T>
T const& doWarning(T const& b)
{
在这里,将被推断为 ,因为这就是传递给它的东西。 通过引用接受其参数,因此 的类型将为 。也就是说,是对 from 的匿名指针的引用:T
A*
doWarning
b
A* const &
b
d
main
b d
┌───────────┐ ┌─────┐ ┌─────┐
│ │ │ │ │ │
│ A* const& ├──────►│ A* ├──────►│ A │
│ │ │ │ │ │
└───────────┘ └─────┘ └─────┘
A* c = returningLocalPointer(b);
在这里,您可以创建另一个指针 ,该指针指向与 相同的对象。我不会看,因为它或多或少无关紧要。这条线可以被替换,什么都不会改变。您的对象现在如下所示:c
b
returningLocalPointer
A* c = b;
b d
┌───────────┐ ┌─────┐ ┌─────┐
│ │ │ │ │ │
│ A* const& ├──────►│ A* ├──────►│ A │
│ │ │ │ │ │
└───────────┘ └─────┘ └─────┘
▲
c │
┌─────┐ │
│ │ │
│ A* ├──────────┘
│ │
└─────┘
如您所见,是与 引用的对象不同的对象。c
b
return c;
由于返回一个 (since is ),这会初始化返回值以引用局部变量:doWarning
A* const&
T
A*
c
b d
┌───────────┐ ┌─────┐ ┌─────┐
│ │ │ │ │ │
│ A* const& ├──────►│ A* ├──────►│ A │
│ │ │ │ │ │
└───────────┘ └─────┘ └─────┘
▲
return value c │
┌───────────┐ ┌─────┐ │
│ │ │ │ │
│ A* const& ├──────►│ A* ├──────────┘
│ │ │ │
└───────────┘ └─────┘
}
现在结束了,因此它的局部变量超出了范围,它的生存期结束了。这样一来,返回值就悬空了:doWarning
c
doWarning
b d
┌───────────┐ ┌─────┐ ┌─────┐
│ │ │ │ │ │
│ A* const& ├──────►│ A* ├──────►│ A │
│ │ │ │ │ │
└───────────┘ └─────┘ └─────┘
return value
┌───────────┐
│ │
│ A* const& ├──────► Nothing here anymore
│ │
└───────────┘
auto m = doWarning(&d);
现在我们回到 . 它本身永远不会推导出引用类型,因此推导的类型为 。这意味着程序将尝试复制返回的引用所引用的指针。但是,引用的返回值的指针不再存在。尝试复制不存在的对象是错误的,如果程序这样做,则其行为是未定义的。m
auto
m
A*
doWarning
doWarning
评论
a