在下面的代码中,为什么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
}
答案 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
返回时不会调用析构函数。