4

这种情况有一个错误:

const int& foo() {
    const int x = 0;
    return x;
}

乃至

const int& foo() {
    const std::pair<int,int> x = {0,0};
    return x.first;
}

但不是这个:

const int& foo() {
    const std::array<int,1> x = {0};
    return x[0];
}

并且(不那么令人惊讶)不是这个:

const int& foo() {
    const std::vector<int> x = {0};
    return x[0];
}

特别是在这种std::vector情况下,我知道这个警告会非常棘手,因为对于编译器来说,const int&返回的std::vector<int>::operator[](size_t) const不是对临时的引用。不过,我实际上对std::array没有失败感到有点惊讶,因为这种类似的情况确实给了我一个错误:

struct X {
    int x[0];
};

const int& foo() {
    X x;
    return x.x[0];
}

是否有任何流行的编译器有可以捕获这些情况的警告/错误?我可以想象一个保守的版本,它会警告返回来自临时成员函数调用的引用。

我用类似下面的方法绊倒了这个问题,在其中我内联了一系列链接的调用,但是因为 C++ 允许您将 locals 分配给const&,所以详细版本可以工作,而表面上相同的版本会立即删除临时,留下一个悬空引用:

#include <iostream>

struct A {
    int x = 1234;
    A() { std::cout << "A::A " << this << std::endl; }
    ~A() { x = -1; std::cout << "A::~A " << this << std::endl; }
    const int& get() const { return x; }
};

struct C { 
    C() { std::cout << "C::C " << this << std::endl; }
    ~C() { std::cout << "C::~C " << this << std::endl; }
    A a() { return A(); }
};

int foo() {
    C c;
    const auto& a = c.a();
    const auto& x = a.get();
    std::cout << "c.a(); a.get() returning at " << &x << std::endl;
    return x;
}

int bar() {
    C c;
    const int& x = c.a().get();
    std::cout << "c.a().get() returning at " << &x << std::endl;
    return x;
}

int main() {
    std::cout << foo() << std::endl;
    std::cout << bar() << std::endl;
}

那输出

C::C 0x7ffeeef2cb68
A::A 0x7ffeeef2cb58
ca(); a.get() 在 0x7ffeeef2cb58 返回
A::~A 0x7ffeeef2cb58
C::~C 0x7ffeeef2cb68
1234
C::C 0x7ffeeef2cb68
A::A 0x7ffeeef2cb58
A::~A 0x7ffeeef2cb58
ca().get() 在 0x7ffeeef2cb58 返回
C::~C 0x7ffeeef2cb68
-1
4

2 回答 2

3

详细版本有效,而表面相同的版本立即删除临时版本,留下悬空引用

你的代码根本不一样。在第一种情况下:

const auto& a = c.a();
const auto& x = a.get();

临时扩展的生命周期为 const 引用的生命周期,所以x只要a有效就有效,但在第二个:

const int& x = c.a().get();

你有悬空的参考x。而且您在此处遇到的情况与您之前显示的示例无关 - 当您返回对局部变量的悬空引用时,如果编译器会检测到您在实际代码中描述的情况,则警告您正在查看的示例几乎不相关。

您的案例的解决方案虽然可以由班级的设计师提出A

struct A {
    int x = 1234;
    A() { std::cout << "A::A " << this << std::endl; }
    ~A() { x = -1; std::cout << "A::~A " << this << std::endl; }
    const int& get() const & { return x; }
    int get() && { return x; } // prevent dangling reference or delete it to prevent compilation
};
于 2018-05-24T19:16:56.237 回答
1

使用生命已结束的值是未定义的行为,请参阅[basic.life]/6.1。该标准不要求编译器为 UB 输出任何诊断信息。

所以你看到的诊断只是编译器的礼貌。很高兴看到你得到了其中的一些,但正如你所注意到的,它们肯定远非无懈可击。

是的,延长寿命是不可链接的。这使得它非常危险和不可靠。

你可以试试 Clang 的 Address Sanitizer (ASAN)。

事实上,ASAN 似乎正在解决您的问题(-fsanitize-address-use-after-scope):

==35463==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7fffffffe970 at pc 0x000000498d53 bp 0x7fffffffe910 sp 0x7fffffffe908
READ of size 4 at 0x7fffffffe970 thread T0
于 2018-05-24T20:26:52.837 回答