悬空指针示例

5
在以下代码中,为什么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


3
不是一个非常“正确”的例子。它说明了悬空指针问题,以及缺乏复制构造函数和复制赋值运算符对保存动态分配内存指针的抽象类的影响,但是从int类型的隐式转换让我感到不安。 - celavek
4个回答

14
问题在于对 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 的指针成员在标记为 XXX 的步骤中被 delete,然后在步骤 YYY 中再次被 delete


3

Nikolai的回答解释了一切,但这里有一个可能的替代方案:

如果您打算让Sample的多个实例共享相同的基础指针,则可以使用像boost::shared_ptr这样的智能指针而不是原始指针。

这会有一点成本,但可能不会比您自己尝试做更多。

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


2
当您调用SomeFunc(Sample x)时,对象x通过调用Sample的复制构造函数创建。由于您没有显式编写复制构造函数,因此编译器创建了一个隐式的复制构造函数。通常,隐式的复制构造函数是可以的:它执行成员逐个复制(如果您使用vector<int>而不是int*,则代码将正常工作)。然而,在这种情况下,它不好使。它只复制了ptr的值,因此现在,x.ptrs1.ptr指向同一个int[]。现在,当SomeFunc结束时,x被销毁,并且其析构函数被调用,意味着ptr被删除。这释放了x使用的内存,但由于它是相同的值,也是s1使用的内存。

1

Nikolai的答案是完全正确的,EreOn也是。

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

如果SomeFunc被声明为:

void SomeFunc(Sample& x)

或者更好的是

void SomeFunc(const Sample& x)

您将不会有悬空指针问题。

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

如果您传递对Sample对象的引用,则在调用SomeFunc时不会进行任何副本,并且因此在SomeFunc返回时不会调用析构函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接