这是一个真正的问题吗:警告 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);
^
是的,这是一个真正的问题。您的程序的行为未定义。 c
与b
所引用的指针是不同的对象,其生命周期结束于doWarning
。这两个指针指向同一个 A
对象 (d
),但这并不意味着它们是同一个对象。
为了说明,我将逐行或多或少地使用图表:
A d;
auto m = doWarning(&d);
这将创建一个名为 d
的 A
对象,并将指向该对象的匿名指针传递给 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* ├──────────┘
│ │
└─────┘
如您所见,c
与 b
引用的对象不同。
return c;
因为 doWarning
return 是一个 A* const&
(因为 T
是 A*
),这初始化了 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);
现在我们回到 m
。 auto
本身永远不会推导出引用类型,因此 m
的类型被推导出为 A*
。这意味着程序将尝试复制 doWarning
returned 的引用所引用的指针。 doWarning
的 return 值引用的指针不再存在。试图复制一个不存在的对象是错误的,如果程序这样做,它的行为是未定义的。
以下代码删除警告 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);
^
是的,这是一个真正的问题。您的程序的行为未定义。 c
与b
所引用的指针是不同的对象,其生命周期结束于doWarning
。这两个指针指向同一个 A
对象 (d
),但这并不意味着它们是同一个对象。
为了说明,我将逐行或多或少地使用图表:
A d;
auto m = doWarning(&d);
这将创建一个名为 d
的 A
对象,并将指向该对象的匿名指针传递给 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* ├──────────┘
│ │
└─────┘
如您所见,c
与 b
引用的对象不同。
return c;
因为 doWarning
return 是一个 A* const&
(因为 T
是 A*
),这初始化了 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);
现在我们回到 m
。 auto
本身永远不会推导出引用类型,因此 m
的类型被推导出为 A*
。这意味着程序将尝试复制 doWarning
returned 的引用所引用的指针。 doWarning
的 return 值引用的指针不再存在。试图复制一个不存在的对象是错误的,如果程序这样做,它的行为是未定义的。