【问题标题】:Is this a real problem: warning C4172: returning address of local variable or temporary这是一个真正的问题:警告 C4172:返回局部变量或临时地址
【发布时间】:2021-12-27 08:30:32
【问题描述】:

以下代码删除警告 C4172:在使用 MSVC 的 Windows 下返回局部变量或临时地址。但我想知道在这种情况下这是否是一个真正的错误?我知道这里有很多类似的主题,并且我从这个警告中阅读了很多类似的主题。所以在这种情况下,返回值是一个来自“main”函数的指针,它应该一直存活到程序结束。如果返回LocalPointer 将返回:“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
}

【问题讨论】:

  • 在这个小sn-p 中使用相同的标识符a 表示三种不同的东西对人类读者没有帮助,是故意的吗?

标签: c++ pointers templates warnings


【解决方案1】:

是的,这是一个真正的问题。您的程序的行为未定义。 c 是与b 引用的指针不同的对象,它的生命周期在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 &amp;。也就是说,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返回一个A* const&amp;(因为TA*),这初始化返回值以引用局部变量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);

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

【讨论】:

  • 首先谢谢你。缺少的一点是,我并不专注于在离开作用域时局部变量会被破坏。这意味着局部变量将首先失效,然后返回。所以返回时将没有可用的局部变量(最坏的情况)。
【解决方案2】:

让我们用T = A*“实例化”这个函数(就像你用doWarning(&amp;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);
       ^

【讨论】:

    【解决方案3】:

    您正在返回对局部变量 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 中存储的值与d 的地址相同,但变量c 本身(即您返回的引用)在函数结束
    猜你喜欢
    • 2019-06-15
    • 2011-04-13
    • 2022-01-17
    • 1970-01-01
    • 1970-01-01
    • 2011-10-17
    • 2013-01-30
    相关资源
    最近更新 更多