悬空指针示例

时间:2010-08-12 13:58:49

标签: c++ dangling-pointer

在下面的代码中,为什么s1.printVal会导致悬空指针错误? s1对象,即它的指针,在它被销毁之前是否仍然可以访问?

class Sample
{
  public:
    int *ptr;
    Sample(int i)
    {
        ptr = new int(i);
    }

    ~Sample()
    {
        delete ptr;
    }
    void PrintVal()
    {
        cout << "The value is " << *ptr;
    }
};

void SomeFunc(Sample x)
{
    cout << "Say i am in someFunc " << endl;
}

int main()
{
    Sample s1 = 10;
    SomeFunc(s1);
    s1.PrintVal(); // dangling pointer
}

Source

4 个答案:

答案 0 :(得分:15)

这里的问题是为<{1}}的参数执行的复制。该副本在销毁时取消分配指针。您还需要实现复制构造函数复制赋值运算符。请参阅rule of three

编辑:

这是“扩展”的伪代码,即编译器在SomeFunc()函数中为您做的事情:

main()

这是不是确切的表示,而是一个概念性的解释。只需考虑编译器与代码有什么关系。

// main addr0 = grab_stack_space( sizeof( Sample )); // alloc stack space for s1 Sample::ctor( addr0, 10 ); // call ctor of Sample addr1 = grab_stack_space( sizeof( Sample )); // alloc stack for argument Sample::ctor( addr1, addr0 ); // call COPY-ctor of Sample SomeFunc( addr1 ); // call SomeFunc Sample::dtor( addr1 ); // XXX: destruct the copy free_stack_space( addr1, sizeof( Sample )); // free stack taken by copy Sample::PrintVal( addr0 ); // call member func on s1 Sample::dtor( addr0 ); // destruct s1 free_stack_space( addr0, sizeof( Sample )); // YYY: free stack taken by s1 的指针成员Sample - 在标有delete的步骤中编辑,然后XXX - 在步骤delete再次编辑。

答案 1 :(得分:3)

尼古拉的答案解释了一切,但这里有一个可能的选择:

如果您希望多个Sample实例共享相同的基础指针,您也可以使用类似boost::shared_ptr的内容而不是原始指针。

这有一点成本,但可能不会超过你自己尝试做的事情。

此外,这将避免编写任何复制构造函数,析构函数和赋值运算符。

答案 2 :(得分:2)

当您致电SomeFunc(Sample x)时,通过调用x的复制构造函数来创建对象Sample。由于您没有显式地编写一个,因此编译器会创建一个隐式的。通常情况下,隐含的一个很好:它会逐个成员复制(如果您使用了vector<int>而不是int*,那么您的代码就会起作用)。但是,在这种情况下,它并不好。它只复制ptr的值,所以现在,x.ptr&amp; s1.ptr指向相同的int []。现在,当SomeFunc结束时,x被销毁,并且在其上调用析构函数,意味着删除了ptr。这释放了x使用的内存,但由于它是相同的值,它也是s1使用的内存。

答案 3 :(得分:1)

尼古拉的回答是绝对正确的。和ereOn一样。

您还需要考虑传值和传递参考之间的区别。

如果SomeFunc被声明为:

void SomeFunc(Sample& x)

甚至更好

void SomeFunc(const Sample& x)

你不会有悬空指针问题。

您定义SomeFunc的方式,Sample对象按值传递,这意味着临时副本将在SomeFunc范围内使用。然后,当SomeFunc返回时,临时对象超出范围并调用其析构函数,这将删除ptr指向的整数。

如果传递对Sample对象的引用,则在调用SomeFunc时不会复制,因此,SomeFunc返回时不会调用析构函数。