是否有C ++警告将引用返回临时?

时间:2018-05-24 18:59:14

标签: c++ compiler-warnings memory-safety

这种情况有错误:

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 ++允许你将本地分配给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
c.a(); a.get() returning at 0x7ffeeef2cb58
A::~A 0x7ffeeef2cb58
C::~C 0x7ffeeef2cb68
1234
C::C 0x7ffeeef2cb68
A::A 0x7ffeeef2cb58
A::~A 0x7ffeeef2cb58
c.a().get() returning at 0x7ffeeef2cb58
C::~C 0x7ffeeef2cb68
-1

2 个答案:

答案 0 :(得分: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
};

答案 1 :(得分:1)

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

因此,您所看到的诊断只是对编译器的礼貌。很高兴看到你那些的那些,但它们肯定远没有你注意到的那么无懈可击。

是的,终身延期不可链接。这使得它非常危险和不可靠。

您可以尝试使用Clang的地址清理程序(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