递归内存“树”:正确释放内存

时间:2020-08-07 19:29:45

标签: c++ recursion memory segmentation-fault c++17

从我的第一堂CS课开始,我想对递归内存分配进行更多练习,因此我决定制作一款名为“递归地牢”的小游戏。它只是允许用户在每次玩家进入一个空(NULL)房间时通过递归生成一个新的“房间”来漫游无限的地牢。

然后保存新房间,可以通过...访问它。

  1. 沿着进入房间时使用的完全相同的路径走下去
  2. 如果您离开房间,请重新回到同一房间

*该程序不使用循环(至少,我认为它不使用循环)。我不熟悉循环的概念以及如何使用它。

我遇到的问题是,当我尝试清理所有递归分配的内存(“房间”)时,收到了经典错误“分段错误:核心已转储”。

下面是我的结构“房间”:


    struct room
    {//begin struct

        room* backward;
        room* left;
        room* forward;
        room* right;

        string desc;
  
        room() { //begin
    
            backward = NULL;
            left = NULL;
            forward = NULL;
            right = NULL;
    
        /*End*/}
 
    /*End struct*/};

每个“房间”都有其他与之相连的房间(左/右/前进/后退)。用户从一个空房间指针“ startingpoint”开始,并且可以沿上述任何一个方向前进。尝试进入一个空(NULL)房间时,会随机生成一个新房间供用户输入。

一旦玩家满意浏览,我将尝试在结束程序之前使用存储所有房间的数组清理分配的内存。而是,它导致分段错误。这是代码:

    void ClearAllocatedMemory(room* aRoom, room** roomArray, int& raIndex) {

        for(short i=0; i<raIndex; i++) {//begin for
    
            delete roomArray[i];
    
        /*End for*/}
    
        delete[] roomArray;

    /*End func*/}

以下是构成我的数组并定义其第一个(第0个)索引的代码:


    room** roomArray;
    int raIndex = 0;

    room* startingpoint = new room();
    roomArray[0] = startingpoint;

下面是将新房间添加到roomArray索引中的代码:


    room* GenRoom(room** roomArray, int& raIndex) {

        room* newroom = new room();
    
        newroom->desc = GenRoomDesc( rand()%12 + 1 );
    
        raIndex++;
    
        roomArray[raIndex] = newroom;

        return newroom;


    }

3 个答案:

答案 0 :(得分:3)

即使可以进行两次删除,您的算法也不起作用。 如果您有两个相互连接的房间,它们将递归地尝试删除彼此,从而导致堆栈溢出。即您无法通过简单的递归删除循环。

您有一个设计问题-谁拥有每个房间?如果用垃圾收集的语言编写这些内容,那么您将不会在乎,而各个房间仅由于它们自身的存在而相互拥有。在C ++中,您必须关心并且设计应该反映这一点。

shared_ptrstd::weak_ptr在这种情况下会很混乱。即使您可以建立树状层次结构并因此使用unique_ptr,也可能仅由于嵌套深层树的析构函数而导致堆栈溢出。

最好和最简单的解决方案是创建一个std::vector<Room>,它是所有房间的明确所有者。邻居然后可以使用该向量的索引。一个警告是,在向量中心处分配空间会使较高的索引无效。可以通过与最后一个元素交换并仅修复其连接来解决。也会从中间的O(1)中删除。

如果您的情况下地图确实是动态的,我可以为std::list<Room>辩护-为邻居使用迭代器或指针-或std::vector<std::unique_ptr<Room>>-使用原始的 non-owning < / strong>指针。

这些仅适用于邻居的解决方案只是一个警告-当玩家循环探索房间时,您没有工具来确定该房间是否已经存在。例如。向上2,向右2,向下2,向左2应该使玩家回到初始房间。您可能要考虑实际使用2D网格(以后再担心内存/性能)。

答案 1 :(得分:2)

如果您的房间图中有一棵普通树(不返回),您的算法将起作用。由于可以返回,因此需要注意不要再次处理已经在处理的房间。

我将展示两种方法,一种对任意图形都有用,另一种仅对带有反向链接的树有用。


方法1.房间类应具有布尔visited标记(游戏者未访问过,但耗尽顺序访问过)。初始值为false。然后,您的删除功能应进行如下修改:

ClearAllocatedMemory(room* aRoom) {
   if (aRoom == nullptr || aRoom->visited) return;
   aRoom->visited = true;
   ClearAllocatedMemory(aRoom->backward);
   ClearAllocatedMemory(aRoom->left);
   ClearAllocatedMemory(aRoom->forward);
   ClearAllocatedMemory(aRoom->right);
   delete room;
}

这实际上将删除过程变成了Depth-first search


方法2.房间删除例程应知道从到达的房间,而不要触摸那个房间。

ClearAllocatedMemory(room* aRoom, room* parent) {
   if (aRoom == nullptr) return;

   if (aRoom->backward != parent)   ClearAllocatedMemory(aRoom->backward, aRoom);
   if (aRoom->left     != parent)   ClearAllocatedMemory(aRoom->left,     aRoom);
   if (aRoom->forward  != parent)   ClearAllocatedMemory(aRoom->forward,  aRoom);
   if (aRoom->right    != parent)   ClearAllocatedMemory(aRoom->right,    aRoom);

   delete room;
 }

这只是正常的从上到下的树遍历,并添加了阻止上升的检查。


第一种方法可能是更可取的,因为它是通用的,您可以将删除例程设为 destructor ,就像在C ++中一样。但这是另一个麻烦。

还请注意,此检查

if ( (aRoom->backward == NULL) && (aRoom->left == NULL)\
        && (aRoom->forward == NULL) && (aRoom->right == NULL) ) {

没有任何意义。每个房间都需要删除,而不仅仅是那些没有传出路径的房间。

答案 2 :(得分:1)

进行更多研究,回顾 stack heap 内存,并通读此article-我想我可能已经在上面找到了问题代码。

更改了清理内存的方式后,从使用递归更改为使用@Quimby建议的数组,我实现了一个room **数组来容纳所有房间。我遇到的问题是我试图删除不应该删除的房间**:不应该将房间**分配给堆栈,而不是,因为我没有使用关键字new来创建它。

因此,要修复我的代码,我只需要从函数中取出delete[] roomArray;