【问题标题】:System.StackOverflowException errorSystem.StackOverflowException 错误
【发布时间】:2014-11-06 17:04:08
【问题描述】:

我正在尝试创建一个 2D 洞穴生成系统。当我运行程序时,在我尝试从自己的类创建新对象之后,我得到 "System.StackOverflowException" 异常。

我的洞穴生成器是这样工作的:

我创建了一张地图,其中包含不同类型单元格(如墙、水或空白空间)的 ID(整数)。

首先我的“地图”类创建了一个充满墙壁的地图,然后在地图的中心,它创建了一个“矿工”对象。矿工挖掘地图并制造洞穴。问题是我想创造更多的矿工。所以,我正在挖掘地图的矿工创建了另一个矿工。但是,当我这样做时,我得到一个 "System.StackOverflowException" 异常。

如何在我的程序中追踪 StackOverflow 的原因。 这是我的矿工代码:

矿工.cs

public class Miner
{
    Random rand = new Random();

    public string state { get; set; }
    public int x { get; set; }
    public int y { get; set; }
    public Map map { get; set; }
    public int minersCount;

    public Miner(Map map, string state, int x, int y)
    {
        this.map = map;
        this.state = state;
        this.x = x;
        this.y = y;
        minersCount++;

        if (state == "Active")
        {
            StartDigging();
        }
    }

    bool IsOutOfBounds(int x, int y)
    {
        if (x == 0 || y == 0)
        {
            return true;
        }
        else if (x > map.mapWidth - 2 || y > map.mapHeight - 2)
        {
            return true;
        }
        return false;
    }

    bool IsLastMiner()
    {
        if (minersCount == 1)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    public void StartDigging()
    {
        if (state == "Active")
        {
            int dir = 0;
            bool needStop = false;
            int ID = -1;

            while (!needStop && !IsOutOfBounds(x, y))
            {
                while (dir == 0)
                {
                    dir = ChooseDirection();
                }

                if (!AroundIsNothing())
                {
                    while (ID == -1)
                    {
                        ID = GetIDFromDirection(dir);
                    }
                }
                else
                {
                    if (!IsLastMiner())
                    {
                        needStop = true;
                    }
                }

                if (ID == 1)
                {
                    DigToDirection(dir);
                    dir = 0;
                }

                if (ID == 0 && IsLastMiner())
                {
                    MoveToDirection(dir);
                    dir = 0;
                }

                TryToCreateNewMiner();
            }

            if (needStop)
            {
                state = "Deactive";
            }
        }
    }

    public void TryToCreateNewMiner()
    {
        if (RandomPercent(8))
        {
            Miner newMiner = new Miner(map, "Active", x, y);
        }
        else
        {
            return;
        }
    }

    bool AroundIsNothing()
    {
        if (map.map[x + 1, y] == 0 && map.map[x, y + 1] == 0 &&
            map.map[x - 1, y] == 0 && map.map[x, y - 1] == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    void MoveToDirection(int dir)
    {
        if (dir == 1)
        {
            x = x + 1;
        }
        else if (dir == 2)
        {
            y = y + 1;
        }
        else if (dir == 3)
        {
            x = x - 1;
        }
        else if (dir == 4)
        {
            y = y - 1;
        }
    }

    void DigToDirection(int dir)
    {
        if (dir == 1)
        {
            map.map[x + 1, y] = 0;
            x = x + 1;
        }
        else if (dir == 2)
        {
            map.map[x, y + 1] = 0;
            y = y + 1;
        }
        else if (dir == 3)
        {
            map.map[x - 1, y] = 0;
            x = x - 1;
        }
        else if (dir == 4)
        {
            map.map[x, y - 1] = 0;
            y = y - 1;
        }
    }

    int GetIDFromDirection(int dir)
    {
        if (dir == 1)
        {
            return map.map[x + 1, y];
        }
        else if (dir == 2)
        {
            return map.map[x, y + 1];
        }
        else if (dir == 3)
        {
            return map.map[x - 1, y];
        }
        else if (dir == 4)
        {
            return map.map[x, y - 1];
        }
        else
        {
            return -1;
        }
    }

    int ChooseDirection()
    {
        return rand.Next(1, 5);
    }

    bool RandomPercent(int percent)
    {
        if (percent >= rand.Next(1, 101))
        {
            return true;
        }
        return false;
    }
}

【问题讨论】:

  • 删除所有与重现问题无关的代码。
  • 你在哪里看到不相关的代码??
  • 控制台 IO?有必要吗?
  • 现在我删除了不相关的代码。
  • StackOverflow StackOverflow 上的异常......只是指出来(我不敢相信花了这么长时间才注意到一个!)我赢了什么吗?跨度>

标签: c# exception system stack-overflow


【解决方案1】:

虽然您可以通过在堆栈上创建太多非常大的对象来获得StackOverflowExceptions,但这通常是因为您的代码已经进入了一次又一次调用相同函数链的状态。因此,要追踪代码中的原因,最好的起点是确定代码调用自身的位置。

您的代码由 Miner 类本身调用的几个函数组成,其中大部分是微不足道的

不调用类中其他任何东西的琐碎函数。虽然这些函数可能有助于触发问题的状态,但它们不是终端函数循环的一部分:

IsOutOfBounds(int x, int y)
bool IsLastMiner()
bool AroundIsNothing()
void MoveToDirection(int dir)
void DigToDirection(int dir)
int GetIDFromDirection(int dir)
int ChooseDirection()
bool RandomPercent(int percent)

剩下的三个函数

public Miner(Map map, string state, int x, int y) // Called by TryToCreateNewMiner
public void StartDigging()                        // Called by constructor
                                                  // Contains main digging loop
public void TryToCreateNewMiner()                 // Called by StartDigging

这三个函数形成一个调用循环,因此如果函数中的分支逻辑不正确,可能会导致非终止循环,从而导致堆栈溢出。

所以,看看函数中的分支逻辑

矿工

根据状态是否为"Active",构造函数只有一个分支。它始终处于活动状态,因为这是始终创建对象的方式,因此构造函数将始终调用StartDigging。这感觉好像状态没有得到正确处理,尽管您将来可能会将它用于其他事情......

顺便说一句,通常认为进行大量处理是不好的做法,而不需要在对象构造函数中创建对象。你所有的处理都发生在感觉错误的构造函数中。

TryToCreateNewMiner

这有一个分支,8% 的时间,它会创建一个新的矿工并调用构造函数。因此,每调用 10 次 TryToCreateNewMiner,我们很有可能它至少会成功一次。新矿工最初在与父对象相同的位置启动(x 和 y 未更改)。

开始挖掘

这种方法有相当多的分支。我们感兴趣的主要部分是调用TryToCreateNewMiner 的条件。让我们看看分支:

if(state=="Active")

目前这是一项冗余检查(它始终处于活动状态)。

while (!needStop && !IsOutOfBounds(x, y)) {

此终止子句的第一部分永远不会触发。 needStop 仅设置为 true if(!IsLastMiner)。由于minersCount 始终为1,因此它始终是最后一个矿工,因此永远不会触发needStop。您使用minersCount 的方式表明您认为它在Miner 的实例之间共享,但事实并非如此。如果这是您的意图,您可能需要阅读 static 变量。

终止子句的第二部分是退出循环的唯一方法,如果 x 或 y 到达地图边缘,则会触发。

while(dir==0)

这是一个毫无意义的检查,dir 只能是 1 到 5 之间的数字,因为这是 ChooseDirection 返回的内容。

if(!AroundIsNothing())

这是检查矿工可以移动的位置是否都设置为0。如果不是,则调用GetIDFromDirection。这是关键。如果 Miner 当前被 0 包围,ID 将不会被设置,它将保持之前的值。在刚刚创建矿工的情况下,这将是-1(我们知道这可能会发生,因为所有矿工都是在创建它的矿工的位置创建的)。

最后两个检查if(ID==1)if(ID==0 && IsLastMiner()) 保护移动矿工的代码(通过调用 dig 或 move)。因此,如果 ID 不是 0 或 1,此时 Miner 将不会移动。这可能会导致问题,因为它是在调用 TryToCreateNewMiner 之前,所以如果程序遇到这种情况,它将陷入矿工不移动的循环中,并且它会不断尝试在相同的位置。 8% 的时间这将起作用,在相同位置创建一个新矿工,它将执行相同的检查并进入相同的循环,再次不动并尝试创建一个新矿工,直到堆栈用完空间和程序崩溃。

您需要查看您的终止条款以及您处理ID 的方式,如果矿工完全被 0 包围,您可能不希望矿工停止做任何事情。

【讨论】:

  • 哦,谢谢你这么好的回答,现在我明白我做错了什么:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-26
  • 2020-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-16
相关资源
最近更新 更多