是否可能存在引用中的分段错误

Is it possible to have a segmentation fault from a reference?

本文关键字:分段 错误 引用 存在 是否      更新时间:2023-10-16

假设以下代码:

Foo & foo = getFoo();
foo.attr; // 100% safe access?

如果foo是一个指针,我会检查它是否是NULL,但因为它是一个引用,所以这样的检查是不必要的。我想知道的是,是否有可能混淆对象的引用,从而使访问其属性变得不安全。

我尝试了一些例子,比如试图将NULL强制转换为Foo对象,但我遇到了编译错误。我只想确保上面的代码始终是安全的,并且不存在我应该意识到的可能的内部C++黑魔法。

根据Benjamin的回答,我可以制作一个示例代码,其中我确实从引用中得到了分段错误,因此它回答了我的问题。我会粘贴我的代码,以防有人对未来感兴趣:

#include <iostream>
using namespace std;
class B
{
    public:
    int x;
    B() {x = 5;}
};
class A
{
    public:
    void f()
    {
        b = *(B*)NULL;
    }
    B & getB()
    {
        return b;
    }
    B b;
};
int main()
{
    A a;
    a.f();
    cout << a.getB().x << endl;
    return 0;
}

是的,这是可能的。

Foo& Fr = *(Foo*)nullptr;

从技术上讲,这已经是未定义的取消引用该指针的行为。但它很可能不会导致任何可观察到的误差。这可能会:

Fr.attr = 10;

然而,正如Jonathan Wakely在评论中指出的那样,你没有理由检查这样的案例。如果函数返回无效引用,则该函数已损坏,需要修复。您的使用代码并没有因为假设引用有效而被破坏。然而,正如David Schwartz的回答中所提到的,在完全合法的代码中,有效的引用可以变得无效(尽管不是null)。但是你没有办法检查这个。你只需要知道在什么情况下会发生这种情况,然后停止使用引用。

当引用位于某个有效对象时,该引用必须引用该对象。这是一个C++标准要求,任何违反它的代码都是UB(未定义的行为),可以做任何事情。

然而,在引用所在之后销毁引用引用的对象是完全合法的。在这一点上,访问引用是非法的。例如:

std::vector<int> j;
j.push_back(3);
int& k = j.front(); // legal, object exists now
j.clear();         // legal, object may be destroyed while reference exists
k++;              // illegal, destruction of object invalidates reference

这意味着返回引用的函数必须始终返回在返回时有效的引用。这就是为什么在空向量上调用front是UB的原因——引用在就位时必须有效。然而,通常会有一些条件会导致该引用无效,如果您计划隐藏该引用并稍后访问它,则需要了解这些条件是什么。

通常,您应该假设将返回的引用隐藏起来并稍后访问它是不安全的,除非您知道该引用仍然有效。例如,std::vector仔细解释了在什么条件下可以使对容器的引用无效,这包括对push_back的后续调用。所以这个坏了:

std::vector<int> j;
j.push_back(3);
int &first = j.front();
j.push_back(4);
int &second = j.back();
if (first == second) // illegal, references into container are invalidated by push_back

但这很好:

std::vector<int> j;
j.push_back(3);
j.push_back(4);
int &first = j.front();
int &second = j.back();
if (first == second) // legal, references into container stay valid

可能存在对坏内存的引用。所以foo.attr; // 100% safe access?的答案是否定的。考虑下面的例子:

int &something() {
    int i = 5, &j = i;
    return j; // Return reference to local variable. This is destroyed at end of scope. 
}
int main() {
    int &k = something(); // Equivalent to getFoo()
    std::cout << k << endl; // Using this reference is undefined behavior. 
    return 0;
}

活生生的例子。

引用k没有指向合法内存。但这仍然会编译。然而,在程序员没有犯错误的情况下,这种情况是不可能发生的。在这种情况下,函数something()写入错误,需要进行修复。没有办法,也没有理由对此进行检查。如果一个函数返回了一个错误的引用,那么你唯一能(也应该)做的就是修复有问题的函数。