这是一个真正的问题:警告 C4172:返回局部变量或临时地址

Is this a real problem: warning C4172: returning address of local variable or temporary

提问人:Gergely Tóth 提问时间:12/27/2021 最后编辑:Alan BirtlesGergely Tóth 更新时间:12/27/2021 访问量:1320

问:

以下代码删除警告 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++ 指针 模板 警告

评论

2赞 MatG 12/27/2021
在这个小片段中对三个不同的事情使用相同的标识符对人类读者没有帮助,这是故意的吗?a

答:

1赞 Alan Birtles 12/27/2021 #1

您将返回对局部变量的引用,因此您的代码具有未定义的行为。你可能会“幸运”,碰巧最终会成为指针,但不能保证。cmd

此代码将定义行为,因为它返回的是引用而不是引用,尽管如果使用临时值调用,它仍然具有未定义的行为(因此可能仍会产生警告):dcdoWarning

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{});
}

评论

0赞 Gergely Tóth 12/27/2021
是的,谢谢,但“c”的地址必须与“d”的地址相同。哪个存在到最后还是?
0赞 Alan Birtles 12/27/2021
不,存储在中的值与 的地址相同,但变量本身(即您返回的引用)在函数末尾被销毁cdc
2赞 Artyer 12/27/2021 #2

让我们用 “实例化” 函数(就像你用T = A*doWarning(&d)

template
A* const& doWarning<A*>(A* const& b)
{
    A* c = returningLocalPointer(&b);
    return c;
}

您也许能够看到问题所在。 通过引用返回,但它是一个立即销毁的局部变量,因此将始终返回一个悬空引用。cdoWarning

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);
       ^
4赞 Miles Budnek 12/27/2021 #3

是的,这是一个真正的问题。程序的行为是未定义的。 是与 引用的指针不同的对象,其生存期在 的末尾结束。这两个指针指向同一个对象 (),但这并不意味着它们是同一个对象。cbdoWarningAd


为了说明这一点,我将或多或少地逐行使用图表:

A d;
auto m = doWarning(&d);

这将创建一个名为的对象,并将指向该对象的匿名指针传递给 。我稍后会谈到,但现在游戏中的对象如下所示:AddoWarningm

               d
┌─────┐       ┌─────┐
│     │       │     │
│  A* ├──────►│  A  │
│     │       │     │
└─────┘       └─────┘

template<typename T>
T const& doWarning(T const& b)
{

在这里,将被推断为 ,因为这就是传递给它的东西。 通过引用接受其参数,因此 的类型将为 。也就是说,是对 from 的匿名指针的引用:TA*doWarningbA* const &bdmain

 b                                 d
┌───────────┐       ┌─────┐       ┌─────┐
│           │       │     │       │     │
│ A* const& ├──────►│  A* ├──────►│  A  │
│           │       │     │       │     │
└───────────┘       └─────┘       └─────┘

A* c = returningLocalPointer(b);

在这里,您可以创建另一个指针 ,该指针指向与 相同的对象。我不会看,因为它或多或少无关紧要。这条线可以被替换,什么都不会改变。您的对象现在如下所示:cbreturningLocalPointerA* c = b;

 b                                 d
┌───────────┐       ┌─────┐       ┌─────┐
│           │       │     │       │     │
│ A* const& ├──────►│  A* ├──────►│  A  │
│           │       │     │       │     │
└───────────┘       └─────┘       └─────┘
                                     ▲
                     c               │
                    ┌─────┐          │
                    │     │          │
                    │  A* ├──────────┘
                    │     │
                    └─────┘

如您所见,是与 引用的对象不同的对象。cb


return c;

由于返回一个 (since is ),这会初始化返回值以引用局部变量:doWarningA* const&TA*c

 b                                 d
┌───────────┐       ┌─────┐       ┌─────┐
│           │       │     │       │     │
│ A* const& ├──────►│  A* ├──────►│  A  │
│           │       │     │       │     │
└───────────┘       └─────┘       └─────┘
                                     ▲
return value         c               │
┌───────────┐       ┌─────┐          │
│           │       │     │          │
│ A* const& ├──────►│  A* ├──────────┘
│           │       │     │
└───────────┘       └─────┘

}

现在结束了,因此它的局部变量超出了范围,它的生存期结束了。这样一来,返回值就悬空了:doWarningcdoWarning

 b                                 d
┌───────────┐       ┌─────┐       ┌─────┐
│           │       │     │       │     │
│ A* const& ├──────►│  A* ├──────►│  A  │
│           │       │     │       │     │
└───────────┘       └─────┘       └─────┘

return value
┌───────────┐
│           │
│ A* const& ├──────► Nothing here anymore
│           │
└───────────┘

auto m = doWarning(&d);

现在我们回到 . 它本身永远不会推导出引用类型,因此推导的类型为 。这意味着程序将尝试复制返回的引用所引用的指针。但是,引用的返回值的指针不再存在。尝试复制不存在的对象是错误的,如果程序这样做,则其行为是未定义的。mautomA*doWarningdoWarning

评论

0赞 Gergely Tóth 12/27/2021
首先谢谢你。缺少的一点是,我并不认为在离开范围时,局部变量可以被破坏。这意味着局部变量将首先失效,然后返回。因此,当返回时,将没有可用的局部变量(最坏情况)。