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

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

以下代码删除警告 C4172:使用 MSVC returning Windows 下局部变量或临时变量的地址。但我想知道在这种情况下这是否是一个真正的错误?我知道这里有很多类似的主题,并且我已经从这个警告中阅读了很多类似的主题。所以在这种情况下,returning 值是来自“main”函数的指针,它应该一直存在到程序结束。如果 returningLocalPointer 会 return: "A something; return &something;"那么是的,这将是一个问题,但在这种情况下,我们 return 一个指针存在,直到“主要”结束。还是我错了?

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

让我们使用 T = A*“实例化”该函数(就像您使用 doWarning(&d) 调用它时所做的那样):

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

你或许能看出问题出在哪里。 c 是通过引用 return 编辑的,但它是一个局部变量,会立即被销毁,因此 doWarning 将始终 return 悬空引用。

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);
       ^

是的,这是一个真正的问题。您的程序的行为未定义。 cb所引用的指针是不同的对象,其生命周期结束于doWarning。这两个指针指向同一个 A 对象 (d),但这并不意味着它们是同一个对象。


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

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

这将创建一个名为 dA 对象,并将指向该对象的匿名指针传递给 doWarning。我稍后会讲到 m,但现在游戏中的对象看起来像这样:

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

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

在这里,T 将被推断为 A*,因为这是传递给它的。 doWarning 通过引用接受其参数,因此 b 的类型将为 A* const &。也就是说,b 是从 main:

指向 d 的匿名指针的引用
 b                                 d
┌───────────┐       ┌─────┐       ┌─────┐
│           │       │     │       │     │
│ A* const& ├──────►│  A* ├──────►│  A  │
│           │       │     │       │     │
└───────────┘       └─────┘       └─────┘

A* c = returningLocalPointer(b);

在这里您创建另一个指针 c,它指向与 b 相同的对象。我不会看 returningLocalPointer,因为它或多或少是无关紧要的。此行可以替换为 A* c = b; 并且不会有任何改变。您的对象现在看起来像这样:

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

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


return c;

因为 doWarning return 是一个 A* const&(因为 TA*),这初始化了 return 值来引用局部变量 c:

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

}

现在 doWarning 结束,所以它的局部变量 c 超出范围并且它的生命周期结束。这使得 doWarning 的 return 值悬而未决:

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

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

auto m = doWarning(&d);

现在我们回到 mauto 本身永远不会推导出引用类型,因此 m 的类型被推导出为 A*。这意味着程序将尝试复制 doWarning returned 的引用所引用的指针。 doWarning 的 return 值引用的指针不再存在。试图复制一个不存在的对象是错误的,如果程序这样做,它的行为是未定义的。