我从 C ++ Primer 5 Edition 中读取了智能指针。在第12.1.3节中,一些描述类似于
智能指针类型定义了一个名为“get”的函数,该函数返回一个指向智能指针正在管理的对象的内置指针。该函数适用于需要将内置指针传递给无法使用智能指针的代码的情况。 使用“get”返回的代码不得“删除”该指针。
下面的描述
也给出了一个例子shared_ptr<int> p(new int(42)); // reference count is 1
int *q = p.get();
{//new block
//undefined:two independent shared_ptr point to the same memory
shared_ptr<int>(q);
}//block ends, q is destroyed ,and the memory to which q points is freed
int foo = *q; //undefined; the memory to which p points was freed
我可以清楚地理解上面的解释。 但是,当我遇到练习12.13 时,我有点困惑。
#include<iostream>
#include<memory>
using namespace std;
int main(){
auto sp = make_shared<int>();
auto p = sp.get();
delete p;
cout <<"Before main function exits!"<<endl; //which I add for debug
return 0;
}
编译时没有错误。但运行
时出现如下错误***Error in './ex12_13': double free or corruption(out): 0x09b97018***
要调试的上下文 在主要功能退出之前! 尚未执行,这意味着错误发生在删除之后我认为操作。我也使用'gdb'调试这个程序,错误就在 delete 之后弹出。
那么,如何解释错误? 删除已经释放了内存,但第二次免费是什么时候发生的?在主要功能退出之前?
我将 sp 的初始化从 make_shared 功能更改为 new 和 使用我自己的删除功能代替删除。
#include<iostream>
#include<memory>
using namespace std;
int main(){
auto deleter = [](int*p){
cout<<"deleter called"<<endl; delete p;
};
shared_ptr<int> sp(new int, deleter);
auto p = sp.get();
delete p;
cout <<"Before main function exits!"<<endl; //which I add for debug
return 0;
}
然后输出结果是
Before main function exits!
deleter called
***Error in './ex12_13_2': double free or corruption(out): 0x08995998***
当程序超出主范围时,将破坏本地变量 shared_ptr p ,并通过调用 deleter <删除指向内存 p 的内存/ em>的。
这就是为什么“在main函数退出之前!”首先显示,删除器名为后来显示,最后是 double free 错误。
所以我认为我上面提出的困惑主要来自make_shared 。 @Ben Voigt给出了详细的解释。
您正在删除一个不是来自new的指针,因此您有未定义的行为(任何事情都可能发生)。
但潜在的原因可能只能从 make_shared 的实现中找到。
答案 0 :(得分:4)
何时第二次自由发生在main函数退出之前?
实际上,它没有。现在,您的程序完全有可能在delete p;
上崩溃,但不是因为双重免费。您正在删除不是来自new
的指针,因此您有未定义的行为(任何事情都可能发生)。特别是,make_shared
通常经过优化,通过将元数据(带引用计数和删除)和对象放在单个分配中来最小化分配。可以使用new[]
进行分配,或者生成的对象指针可以位于分配的中间,而不是开头。在任何一种情况下,直接尝试delete
对象(即使释放智能指针以避免将来的第二次释放)也只是麻烦。
但是,我们无法确定崩溃的确切位置,因为您的测试代码存在一个小缺陷。
cout <<"Before main function exits!"<<endl;
不会立即显示文本,只是将其添加到stdout
缓冲区。由于程序崩溃,缓冲区实际上永远不会写入关联的文件描述符。
对于“printf debugging”,始终使用无缓冲的流,例如cerr
cerr << "Before main function exits!" << endl;
然后你会看到事件的真实顺序。