【问题标题】:Double Free Error双重免费错误
【发布时间】:2011-11-06 08:24:56
【问题描述】:

我为一个名为 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();
}

我觉得我错过了一些明显的东西。有人可以向我指出析构函数是如何被调用两次的吗?

【问题讨论】:

  • 需要更多代码才能确定,但​​您的 setMap 调用似乎正在对它所拥有的一些内存进行浅拷贝。
  • 在您的result.setMap(map) 中,它是否对map 进行了深层复制?如果没有,那么你的问题就在那里。换句话说,Chad 所说的 (+1)。
  • 我需要看看你的 setMap() fn。
  • 什么是map,为什么不使用资源管理智能指针?
  • 顺便说一句:这看起来像是将 Java 翻译成 C++。您通常不需要在 C++ 中编写 copy() 方法 - 您只需编写一个复制构造函数,然后在实例上使用赋值运算符,例如。网格 g = *(myServer->getAgent()->getGrid());

标签: c++ c memory free dealloc


【解决方案1】:

您的复制功能不会创建地图的深层副本;它只是复制指针映射包含。当对原始对象和副本调用析构函数时,这些指针将被删除两次。

【讨论】:

    【解决方案2】:

    您缺少复制构造函数和赋值运算符。这是Law of the Big Three

    三巨头是:

    1. 复制构造函数
    2. 赋值运算符
    3. 析构函数

    三巨头的法则是,如果您需要其中之一,那么您很有可能需要全部三者。它们通常涉及以不平凡的方式处理资源。

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

    【讨论】:

      【解决方案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 在 cmets 中提到的深度复制地图。或者,您也可以在其上使用 shared_ptr 以节省复制成本。

      【讨论】:

      • 这并不能解决问题。它只是延长它直到你打电话给delete result;
      • @Toolbox:是的,编辑了我的答案以提及代码中的另一个错误
      • 返回一个对象有什么问题?为什么返回一个指针,智能或其他,更好?
      【解决方案4】:

      您实际上根本不想要copy 方法。您只需要一个复制构造函数和赋值运算符。我猜你的台词最初是这样的:

      g = myServer->getAgent()->getGrid();
      

      由于这不起作用,您添加了复制方法。但是现在这样,单独修复复制方法并不能解决问题,因为您还需要复制构造函数和赋值运算符,否则您修复复制方法的辛勤工作可能会被破坏。

      首先,简要解释一下发生了什么,以及程序失败的原因:

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

      如您所见,有 3 个地方发生了浅拷贝——必须全部修复,否则仍然会失败。

      如何解决这个问题

      1. 摆脱 copy 方法 - 无论如何它不提供任何价值。
      2. 修复您的 setMapsetFilename 以进行深层复制。
      3. 创建一个赋值运算符。这应该深度复制另一个 Grid 的内容。
      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 对象。因此,通过足够的编译器优化,您的代码可能会超过这一点并在以后崩溃。显然这没有帮助,所以这只是一个参考。

      【讨论】:

        【解决方案5】:

        您发布的代码中缺少重要部分,例如类定义和 setMap 的实现。尽管如此,我可以从我所看到的推断出,当编译器为临时对象(返回值)调用默认复制构造函数时,可能会发生问题。您最终拥有两个 Grid 对象,其映射成员指向仅分配一次的同一内存,显然它们各自的析构函数调用将发生冲突。如果您的班级中有任何动态分配的成员(如 map 似乎是),您必须执行以下操作之一:

        a) 完全支持值语义,实现默认和复制构造函数以及赋值运算符 b) 通过声明(而不是定义)复制构造函数和赋值运算符私有来禁止它。

        如果您的对象创建成本高(在内存和执行时间上),选项 b) 是首选。

        如果选择 (a),则不需要 copy() 方法,只需进行赋值即可。 如果你选择 (b),你的 copy() 方法应该返回一个指针(最好是智能指针),例如 dark_charlie。在这种情况下,我还建议将 copy() 重命名为 clone(),这是此类方法名称的最流行约定。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-09-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多