2017-03-06 49 views
4
// 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++最初没有抱怨,但地址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"}; 
    } 

但是,这并留在我脑海中的一些问题:

  1. 为什么g++抱怨的问题,直到-O2加入?
  2. 究竟是什么关于我的代码是未定义?我会说这是明确的:B::operator() const是......好的......常量。这应该表示中使用的对象不是本地人就是const成员。它访问const成员。它构造了一个局部变量const auto &,它应该引用一个const成员字段。究竟是什么导致它绑定到临时?
+1

为什么参考首先被返回? – chris

+0

这个例子减少了;我使用'operator()'作为选择器函数来决定将哪个成员变量用作另一个函数的输入。当我不需要修改它时,为什么要返回一个非const(或特别是非const的引用)? – inetknght

+1

通过'const'值返回有普遍的缺点。首先,调用者的返回值是孤立的,所以对它的修改无关紧要。其次,它抑制RVO。无论如何,如果您在实际代码中使用初始化程序列表,那么结果并不会很好,因为这些程序需要复制,除非语言最终更改为允许移动。 – chris

回答

5
  1. 编译器没有义务出具的诊断为未定义行为。如果编译器能够检测到在语法上有效的代码,但是会导致未定义的行为,然后抱怨它,那只会是锦上添花。海湾合作委员会的-O2打开额外的优化,并进行额外的代码分析;因为只有启用了优化,gcc才能够检测未定义的行为。

  2. 看来,你的范围迭代在临时std::initializer_list。范围迭代变量是对初始化程序列表的引用。因此,这个函数最终返回一个临时引用,这是gcc在这里咆哮的。由于临时方法在方法返回时被销毁,该方法最终返回一个对被销毁对象的引用。任何对该引用的使用都包含未定义的行为。

当您转换临时范围指针列表,你是按值迭代,而不必返回到一个临时的引用,而是derefencing范围内的值,这是一个完美的洁净指针。

,请注意以下Blurb的from here:

The underlying array is not guaranteed to exist after the lifetime of the original initializer list object has ended. The storage for std::initializer_list is unspecified (i.e. it could be automatic, temporary, or static read-only memory, depending on the situation).

如果绑定范围迭代初始化列表在迭代结束结束寿命。其中包括从方法返回。因此,初始化程序列表不再存在,并且您刚刚返回了一个悬挂引用。

+3

也许应该清楚,'initializer_list'是按值存储对象,而不是通过引用(和奖金指出为什么这是一个blurb)。 –

+0

是的,如果我看到标准中引用的内容可能会标记为我的答案。我当然喜欢更具体的东西。临时初始化列表,当然...但为什么暂时不能包含对现有对象的引用?当我通过在初始化列表中添加一个显式类型'std :: initializer_list {...}'来尝试它时,它会出错:'/usr/include/c++/6.3.1/ initializer_list:54:26:error:形成指向引用类型'const boost :: filesystem :: path&'的指针,因为形成了一个迭代器到const引用的指针。猜猜这是什么? – inetknght

+0

我引用的同一个链接指出初始化列表的内容总是“复制初始化”,这使得它们不能被引用。虽然应该可以从标准中挖掘出相关的位,但这总是一件苦差事,而cppreference.com在这里通常被认为是“足够好”的。 –