ReadingLab / Discussion-for-Cpp

C++ 中文讨论区
MIT License
89 stars 63 forks source link

Ex12.11解释问题 #23

Closed TonyChouZJU closed 9 years ago

TonyChouZJU commented 9 years ago

从程序的运行上来看,运行后,的确会有double free or corruption的错误。

As a result, at end of this main function p will free the memory that has been freed inside process (). That's why "double freed or corruption" was generated.

从这个解释上来看,是 main 函数退出时,p 原来指向的内存被再次释放导致的。 但是,如果测试程序如果在 process(std::shared_ptr<int>(p.get())); 后加上一句比如 cout<<"exit main"<<endl; 会发现并没有输出 exit main 这句话就已经报错 double free or corruption。 同样,如果 gdb 一下,这个错误也是 process 一返回就报错的。

shared_ptr<int> p(new int(42));share_ptr<int>(p.get()) 是两个独立的 shared_ptr 指向相同的内存,process 退出时会销毁函数的局部变量,指向的内存 int(42) 同样被释放,感觉没有double free啊 不知道问题出在了哪里??

pezy commented 9 years ago

@TonyChouZJU 你好,请问下你的 gcc 是哪个版本的呢? 我用 4.9.1 并未重现。在 VS 下试了,也未出现。比较好奇。

我比较倾向于你摘出来的这个解释( @Mooophy ),应该是 p 指向的 int 对象被释放了两次导致的。。

Mooophy commented 9 years ago

vs2013,没有复现。

TonyChouZJU commented 9 years ago

@pezy @Mooophy 我是在linux 下使用gcc 4.8.2出现的. 同样的问题更明显的出现在练习12.13 这道题的解答为什么要加上一个{}使得它变成一个代码块呢?

include

include

int main() { { auto sp = std::make_shared(); auto p = sp.get(); delete p; }

return 0;

}

我之后做了一些尝试,将这个{}去掉,在delete p;加上一句比如cout<<"before main exits!"<<endl; 从结果看,对于用new来初始化的shared_ptr, shared_ptr sp = new int; auto p = sp.get(); delete p; cout<<"before main exits!"<<endl; 在没有退出代码块的情况下,是不会有double free的error的,只是sp指向的内存已经被释放掉了,可以输出"before main exits!"。当sp这个局部变量离开main函数后,才会报出double free的错误。 而 shared_ptr sp = make_shared(); auto p = sp.get(); delete p; cout<<"before main exits!"<<endl; 会发现没有输出before main exits!,在delete p后立马报错double free.

我在stackoverflow上贴了一个详细的问题,希望能够讨论下http://stackoverflow.com/questions/30294604/what-if-i-delete-the-pointer-that-the-smart-pointer-is-managing

TonyChouZJU commented 9 years ago

@pezy @Mooophy 二位大大所说的没有出现是指的是?你们在process函数外加上cout<<"exit main"<<endl; 可以输出exit main么?

include

include

include

include

void process(std::shared_ptr ptr) { std::cout << "inside the process function:" << ptr.use_count() << "\n"; }

int main() { std::shared_ptr p(new int(42)); process(std::shared_ptr(p.get())); cout<<"exit main"<<endl; return 0; }

pezy commented 9 years ago

@TonyChouZJU 是的呢,可以输出的。我在 gcc 4.8.1 也试了一次,也是可以输出。

还试了在线的:http://coliru.stacked-crooked.com/a/6b991a8ead6e8975

都可以输出。。。

pezy commented 9 years ago

会发现没有输出before main exits!,在delete p后立马报错double free.

这个问题书上早就说明,对 get() 返回的指针进行 delete 是不被允许的,请见:

Caution: Smart Pointer Pitfalls Smart pointers can provide safety and convenience for handling dynamically allocated memory only when they are used properly. To use smart pointers correctly, we must adhere to a set of conventions:

  • Don’t use the same built-in pointer value to initialize (or reset) more than one smart pointer.
  • Don’t delete the pointer returned from get().
  • Don’t use get() to initialize or reset another smart pointer.
  • If you use a pointer returned by get(), remember that the pointer will become invalid when the last corresponding smart pointer goes away.
  • If you use a smart pointer to manage a resource other than memory allocated by new, remember to pass a deleter
TonyChouZJU commented 9 years ago

@pezy 囧 写错了new出来的是可以输出的,我想问的是make_shared int main() { std::shared_ptr p=make_shared(100); process(std::shared_ptr(p.get())); cout<<"exit main"<<endl; return 0; }

pezy commented 9 years ago

@TonyChouZJU

原来如此,那我可能知道问题在哪了。

你清楚 std::shared_ptr<int> p(new int(100));make_shared<int>(100) 的区别不?

前者其实分配了两次内存,一次为匿名对象分配,一次为引用计数分配。后者却仅有引用计数分配。

引用计数分配的内存,是由智能指针自身管理的,当 use_count 变成 0,则回收内存。你给 process 传参的时候,又弄了一个新的局部智能指针来管理同一份内存。当该局部智能指针的 use_count 变成 0 的时候,它是要回收所管理的内存的,可是显然这份内存还有一个管理者:外部的智能指针 p。造成了内存错误(double free)。

如果采用第一种方式,外部智能指针 p 实际管理着两份内存,一是为匿名对象所分配的,另一份是智能指针引用计数所管理的。所以此刻你调用 p->use_count() 会得到 1,因为你只看的到 1 份计数。同样的,在 process 结束的时候,删掉了一份内存,但此刻不会有内存错误,因为 p 其实还管理着一份内存,只不过他对其一无所知,你用 *p 访问其值的时候,会发现是一个随机值。而继续用 p->use_count() 依然会得到 1,这乍一看很诡异,但你结合上面内存分配两次这个事实,就比较好理解了。

综上,请避免使用第一种方式为智能指针初始化,尽量使用 make_shared<int> 的方式。


Reference: