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++最初不会抱怨,但 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"};
    }

但是,这确实在我心中留下了一些问题:

  1. 为什么在添加之前g++ 抱怨问题-O2
  2. 我的代码到底是什么未定义?我会说它是明确定义的:B::operator() const是......好吧...... const。这应该意味着它内部使用的对象要么是本地人,要么是 const 成员。它访问 const 成员。它构造了一个局部变量const auto &……应该引用一个 const 成员字段。究竟是什么导致它绑定到临时的呢?
4

1 回答 1

5
  1. 编译器没有义务为未定义的行为发出诊断。如果编译器可以检测到语法上有效但导致未定义行为的代码,然后抱怨它,那只是锦上添花。gcc-O2开启了额外的优化,并进行额外的代码分析;因此,gcc 有时只能在启用优化的情况下检测未定义的行为。

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

当您将临时范围转换为指针列表时,您正在按值进行迭代,并且您不会返回对临时的引用,而是取消对范围中的值的引用,这是一个完美的犹太指针。

请注意此处的以下内容:

在原始初始化列表对象的生命周期结束后,不能保证底层数组存在。std::initializer_list 的存储是未指定的(即它可以是自动的、临时的或静态的只读存储器,具体取决于情况)。

如果初始化列表绑定到范围迭代,则生命周期在迭代结束时结束。其中包括从方法返回。因此,初始化列表不再存在,而您只是返回了一个悬空引用。

于 2017-03-06T03:14:58.207 回答