System.StackOverflowException错误

时间:2014-11-06 17:04:09

标签: c# exception system stack-overflow

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

我的洞穴发生器的工作原理如下:

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

首先关闭我的所有" Map" class创建一个充满墙壁的地图,然后在地图的中心创建一个" Miner"宾语。矿工挖掘地图并制作洞穴。问题是我想创造更多的矿工。所以,我挖掘地图的Miner创造了另一个矿工。但是,当我这样做时,我得到一个" System.StackOverflowException" 异常。

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

Miner.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;
    }
}

1 个答案:

答案 0 :(得分:5)

虽然你可以通过在堆栈上创建太多非常大的对象来获得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%的时间,它将创建一个新的矿工并调用构造函数。因此,每调用TryToCreateNewMiner 10次,我们就有机会至少成功一次。新矿工最初在与父对象相同的位置启动(x和y未更改)。

<强> StartDigging

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

if(state=="Active")

目前这是一项冗余检查(它始终有效)。

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

此终止条款的第一部分从未被触发。 needStop只能设置为true if(!IsLastMiner)。由于minersCount始终为1,因此它始终是最后一个矿工,因此永远不会触发needStop。您使用minersCount的方式表明您认为它是Miner的实例之间共享,而不是static。如果这是您的意图,您可能需要阅读while(dir==0)变量。

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

dir

这是一项毫无意义的检查,ChooseDirection只能是介于1到5之间的数字,因为if(!AroundIsNothing())返回了这个数字。

ID

这是检查Miner可以进入的位置是否都设置为0.如果不是,则调用GetIDFromDirection。这是关键。如果Miner当前被0包​​围,则-1将不会被设置,它将保持其先前的值。在刚刚创建了Miner的情况下,这将是if(ID==1)(我们知道这可能发生,因为所有Miners都是在Miner创建它的位置创建的。)

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

您需要查看一下您的终止条款以及您处理{{1}}的方式,如果它完全被0环绕,您可能不希望Miner停止做任何事情