双重自由错误
Double Free Error
我为一个名为copy()的对象创建了一个函数,它应该只返回一个具有所有相同值的对象的实例
Grid Grid::copy() {
Grid result;
result.setFilename(f_name);
result.setNumOfRows(num_rows);
result.setNumOfCols(num_cols);
result.setMap(map);
return result;
}
析构函数是这样的-
Grid::~Grid() {
for(int r=0;r<num_rows;r++)
delete [] map[r];
}
现在,每当我的代码运行并且调用复制函数时,我都会得到一个错误
*** glibc detected *** ./go: double free or corruption (!prev): 0x0982c6a8 ***
后面有很多其他信息(一大块文字)。这就意味着内存被删除了两次,对吗?如果是这样,怎么会这样呢?为什么析构函数被调用两次?
它被调用的代码如下-
for(;;) {
Grid g;
if(which_display == 1) {
.....
.....
g = myServer->getAgent()->getGrid()->copy(); //HERE
}
//print
std::cout<<g.toString();
}
我觉得我错过了一些明显的东西。有人能告诉我析构函数是如何被调用两次的吗?
你的copy函数没有创建map的深度拷贝;它只是复制指针映射包含的内容。当对原始对象和副本调用析构函数时,这些指针将被删除两次。
缺少复制构造函数和赋值操作符。这是三巨头的法则。
三巨头是:
- 拷贝构造函数 赋值操作符
- 析构函数
三大法则是,如果你需要其中一个,那么很有可能你需要所有三个。它们通常涉及以一种不平凡的方式处理资源。
在你的例子中,你显式地释放析构函数中的内存。这可能意味着您需要在复制构造函数和赋值操作符中专门处理内存:要么正确分配新内存并复制值,要么阻塞复制和赋值(通过将它们声明为私有而不实现它们)。
您正在从copy
函数返回一个临时对象。你可能想要的是在堆中分配Grid,然后传递一个指针(或者更好的是,一个智能指针):
Grid *Grid::copy() {
Grid *result = new Grid();
result->setFilename(f_name);
result->setNumOfRows(num_rows);
result->setNumOfCols(num_cols);
result->setMap(map);
return result;
}
智能指针版本(您也可以在c++ 11中使用std::shared_ptr
):
boost::shared_ptr<Grid> Grid::copy() {
boost::shared_ptr<Grid> result(new Grid());
result->setFilename(f_name);
result->setNumOfRows(num_rows);
result->setNumOfCols(num_cols);
result->setMap(map);
return result;
}
在你发布的代码中,当函数退出时,结果被销毁,并且你得到未定义的行为。
编辑:也要确保像Chad在评论中提到的那样深度复制地图。或者,您也可以在其上使用shared_ptr来节省复制成本。
您实际上根本不需要copy
方法。您只需要一个复制构造函数和赋值操作符。我猜你的台词本来是这样的:
g = myServer->getAgent()->getGrid();
由于这不起作用,您添加了copy方法。但是现在的情况是,仅仅修复你的复制方法并不能解决问题,因为你还需要复制构造函数和赋值操作符,否则你修复复制方法的辛苦工作可能会被破坏。
首先,快速解释正在发生的事情以及程序失败的原因:
- 呼叫
copy
- 进入copy方法,在堆栈上创建一个
Grid
。 - 设置
Grid
的成员,但我们怀疑它做了一个浅拷贝。 - 复制方法返回,调用
Grid
的复制构造函数。* - 默认的复制构造函数做一个浅拷贝。
- 基于堆栈的
Grid
的析构函数触发,删除map
的内容。 - copy方法现在已经返回,提供了一个临时的
Grid
,但是它指向已删除的内存。现在临时的Grid
对象被赋值给g
。这将调用Grid
的赋值操作符。* - 默认赋值操作符执行浅复制,就像默认复制构造函数一样。
- 在该行的末尾,临时对象被销毁,它试图删除
map
的内容——这些内容已经被删除了。繁荣。 - 当
g
超出作用域时,其析构函数将再次尝试删除map
的内容。
正如你所看到的,有3个地方发生了浅拷贝——所有这些都必须修复,否则这仍然会失败。
如何修复
- 去掉copy方法——反正它也不提供任何值。
- 修复你的
setMap
和setFilename
做深度复制。 - 创建赋值操作符。这应该深拷贝其他网格的内容。
- 创建复制构造函数,就像赋值操作符一样。
下面是赋值操作符的样子(假设所有set方法都进行深度复制):
Grid& operator= (const Grid& g) {
setFilename(f_name);
setNumOfRows(num_rows);
setNumOfCols(num_cols);
setMap(map);
return *this;
}
有一些技术可以编写复制构造函数,然后使赋值操作符使用复制构造函数。这是一种很好的技术(减少重复的代码),但是我手头没有链接。当我找到它的时候,我会把它链接到这里。
最后,我在我的解释中标记了几行(*)。编译器可以做返回值优化(RVO),并命名为RVO。通过这些优化,它实际上不会在copy
的堆栈上创建Grid
对象,然后为返回值复制构造——它只会为copy
的结果创建临时对象,然后copy
方法将使用该对象,而不是使用它自己的内部基于堆栈的Grid
对象。因此,通过充分的编译器优化,您的代码可能会通过这一点,然后崩溃。很明显这是没有用的,所以这只是一个参考。
您发布的代码中缺少重要的部分,例如类定义和setMap的实现。尽管如此,我可以从我所看到的推断出,问题可能发生在编译器为临时对象(返回值)调用默认复制构造函数时。最终会得到两个Grid对象,它们的map成员指向只分配一次的同一内存,显然它们各自的析构函数调用会发生冲突。如果你的类中有任何动态分配的成员(就像map一样),你必须做这些事情之一:
a)完全支持值语义,实现默认构造函数和复制构造函数以及赋值操作符b)通过声明(而不是定义)复制构造函数和赋值操作符private来禁止它。
选项b)是最好的选择,如果你的对象是昂贵的(内存和执行时间)创建。
如果你选择(a),你不需要copy()方法,只需要赋值。如果你选择(b),你的copy()方法应该返回一个指针(最好是一个智能指针),如dark_charlie所示。在这种情况下,我还建议将copy()重命名为clone(),这是此类方法名称中最流行的约定。
- 创建 OpenCV 非自由版本 v4.3 时出错,可折叠.cpp错误 C2039、2605
- 瓦尔格林德错误 - 地址0x0不是堆叠的 malloc'd 或自由的
- Malloc后的自由功能错误
- 双自由错误而不是分割错误
- 使用GDB修复大型项目中的双自由或损坏(!prev)错误
- C++内存分配/释放和自由空间错误
- c++双自由或损坏(fasttop)--不确定错误在哪里
- OpenCV 编译错误与非自由/feature2d.hpp.
- 当我在c++中擦除向量时,我遇到了双自由错误
- C++glibc双自由错误
- 具有自由功能常量和非常量过载的增强型纠删错误
- 自由图像报告图像颜色类型错误
- 为什么在运算符重载中会出现自由错误
- 为什么会出现这种双自由错误
- C++Glibc检测到错误.双重自由或腐败
- 在自由函数中定义的类型,可通过自动外部访问.语言错误或功能
- glibc在运行C++代码时检测到*双自由或损坏错误
- C++中的双自由错误
- 无法找出双重自由或腐败(fasttop)错误
- 为什么编译器不会自动内联自由定义的函数?而是导致链接器错误