双重自由错误

Double Free Error

本文关键字:错误 自由      更新时间:2023-10-16

我为一个名为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的深度拷贝;它只是复制指针映射包含的内容。当对原始对象和副本调用析构函数时,这些指针将被删除两次。

缺少复制构造函数和赋值操作符。这是三巨头的法则。

三巨头是:

  1. 拷贝构造函数
  2. 赋值操作符
  3. 析构函数

三大法则是,如果你需要其中一个,那么很有可能你需要所有三个。它们通常涉及以一种不平凡的方式处理资源。

在你的例子中,你显式地释放析构函数中的内存。这可能意味着您需要在复制构造函数和赋值操作符中专门处理内存:要么正确分配新内存并复制值,要么阻塞复制和赋值(通过将它们声明为私有而不实现它们)。

您正在从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方法。但是现在的情况是,仅仅修复你的复制方法并不能解决问题,因为你还需要复制构造函数和赋值操作符,否则你修复复制方法的辛苦工作可能会被破坏。

首先,快速解释正在发生的事情以及程序失败的原因:

  1. 呼叫copy
  2. 进入copy方法,在堆栈上创建一个Grid
  3. 设置Grid的成员,但我们怀疑它做了一个浅拷贝。
  4. 复制方法返回,调用Grid的复制构造函数。*
  5. 默认的复制构造函数做一个浅拷贝。
  6. 基于堆栈的Grid的析构函数触发,删除map的内容。
  7. copy方法现在已经返回,提供了一个临时的Grid,但是它指向已删除的内存。现在临时的Grid对象被赋值给g。这将调用Grid的赋值操作符。*
  8. 默认赋值操作符执行浅复制,就像默认复制构造函数一样。
  9. 在该行的末尾,临时对象被销毁,它试图删除map的内容——这些内容已经被删除了。繁荣。
  10. g超出作用域时,其析构函数将再次尝试删除map的内容。

正如你所看到的,有3个地方发生了浅拷贝——所有这些都必须修复,否则这仍然会失败。

如何修复

  1. 去掉copy方法——反正它也不提供任何值。
  2. 修复你的setMapsetFilename做深度复制。
  3. 创建赋值操作符。这应该深拷贝其他网格的内容。
  4. 创建复制构造函数,就像赋值操作符一样。

下面是赋值操作符的样子(假设所有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(),这是此类方法名称中最流行的约定。