ranged for 变量循环导致返回局部变量的地址引用?
Posted
技术标签:
【中文标题】ranged for 变量循环导致返回局部变量的地址引用?【英文标题】:ranged for loop of variables results in returning address-reference of local variable? 【发布时间】:2017-03-06 03:06:09 【问题描述】:// g++ --std=c++17 test.cpp -I /usr/local/include -L /usr/local/lib -lboost_system -Wall -pedantic -Wreturn-type -Wstrict-aliasing -Wreturn-local-addr -fsanitize=address -g
// LD_LIBRARY_PATH=/usr/local/lib ./a.out
#include <iostream>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
class A
public:
fs::path path_;
const fs::path & path() const return path_;
fs::path & path() return path_;
;
class B
public:
fs::path root_path_;
A path_2;
A path_3;
const fs::path & operator()() const
for ( const auto & path :
path_3.path(),
path_2.path(),
root_path_
)
if ( not path.empty() )
return path;
throw std::logic_error"using default-constructed B";
;
int main(int argc, char **argv)
B b;
b.root_path_ = "foo/";
b.path_2.path() = "foo/bar";
b.path_3.path() = "foo/baz";
std::cout << b() << '\n';
return 0;
使用上面的代码,据我所知,它似乎是有效的 C++。相反,当被调用时,我得到了垃圾输出。
g++
最初并没有抱怨,但是 Address Sanitizer 会。 g++
最后在添加-O2
时抱怨。生成的警告是
test.cpp: In member function ‘const boost::filesystem::path& B::operator()() const’:
test.cpp:31:12: warning: function may return address of local variable [-Wreturn-local-addr]
return path;
^~~~
test.cpp:29:3: note: declared here
)
^
请注意,我正在使用:
$ cat /etc/fedora-release
Fedora release 25 (Twenty Five)
$ g++ --version
g++ (GCC) 6.3.1 20161221 (Red Hat 6.3.1-1)
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
请注意,我通过使用指针解决了 错误。
const fs::path & operator()() const
for ( const auto * path :
&path_3.path(),
&path_2.path(),
&root_path_
)
if ( not path->empty() )
return *path;
throw std::logic_error"using default-constructed B";
但是,这确实在我心中留下了一些问题:
-
为什么
g++
没有在添加-O2
之前抱怨这个问题?
究竟我的代码未定义呢?我会说它定义明确:B::operator() const
是......好吧...... const。这应该意味着它里面的对象 used 要么是本地人,要么是 const 成员。它访问 const 成员。它构造了一个局部变量const auto &
,它...应该引用一个const成员字段。究竟是什么原因导致它改为临时绑定?
【问题讨论】:
为什么首先返回引用? 这个例子被精简了;我使用operator()
作为选择器函数来决定将哪个成员变量用作另一个函数的输入。当我不需要修改非常量(或特别是非常量引用)时,为什么要返回它?
由const
值返回具有普遍的缺点。首先,调用者的返回值是隔离的,所以函数修改与否无关紧要。其次,它抑制 RVO。无论如何,如果您在实际代码中使用初始化列表,那么结果不会很好,因为除非最终更改语言以允许移动,否则它们需要复制。
对于much shorter example
【参考方案1】:
编译器没有义务为未定义的行为发出诊断。如果编译器可以检测到语法上有效但导致未定义行为的代码,然后抱怨它,那只是锦上添花。 gcc 的-O2
开启了额外的优化,做额外的代码分析;因此,gcc 有时只能在启用优化的情况下检测未定义的行为。
您的范围迭代似乎已超过临时std::initializer_list
。范围迭代变量是对初始化列表的引用。因此,该函数最终返回对临时对象的引用,这就是 gcc 在这里咆哮的内容。由于在方法返回时临时对象被销毁,因此该方法最终返回对已销毁对象的引用。对该引用的任何使用都包含未定义的行为。
当您将临时范围转换为指针列表时,您正在按值进行迭代,并且您不会返回对临时的引用,而是取消引用范围中的值,这是一个完美的犹太指针。
注意以下简介from here:
底层数组在生命周期后不保证存在 原始初始化列表对象已结束。存储为 std::initializer_list 未指定(即它可以是自动的, 临时或静态只读存储器,视情况而定)。
如果绑定到范围迭代的初始化列表在迭代结束时结束,则生命周期。其中包括从方法返回。因此,初始化列表不再存在,而您只是返回了一个悬空引用。
【讨论】:
也许应该明确一点,initializer_list
是按值存储对象,而不是按引用(以及关于为什么会这样的宣传的奖励积分)。
是的,如果我看到标准中引用的内容可能会被标记为我的答案。我当然想要更具体的东西。一个临时初始化列表,当然……但为什么临时不能包含对现有对象的引用?当我通过在初始化列表前面加上显式类型std::initializer_list<const boost::filesystem::path&>...
来尝试它时,它会出错:/usr/include/c++/6.3.1/initializer_list:54:26: error: forming pointer to reference type ‘const boost::filesystem::path&’
因为形成了一个指向 const 引用指针的迭代器。猜猜_就这样?
我引用的同一个链接指出初始化列表的内容始终是“复制初始化”的,这使它们无法被引用。虽然应该可以从标准中挖掘出相关的部分,但这总是一件苦差事,而 cppreference.com 在这里通常被认为“足够好”。以上是关于ranged for 变量循环导致返回局部变量的地址引用?的主要内容,如果未能解决你的问题,请参考以下文章
为什么递归循环中的局部变量被忽略? For循环中的Java递归循环