как я могу поймать корень исключения stackoverflow в рекурсивном коде

У меня есть следующий рекурсивный код, и я получаю исключение stackoverflow. Я не могу понять основную причину,потому что как только я получаю исключение, я не получаю полный стек вызовов в Visual studio.

идея в том, что есть команды организации, которые сворачиваются в более крупные "основные" команды.

кто-нибудь видит недостаток в этом коде ниже, который может быть виновником?

    private Unit GetUnit(Unit organisationalUnit)
    {
        if (organisationalUnit.IsMainUnit)
        {
            return organisationalUnit;                
        }

        if (organisationalUnit.Parent == null)
            return null;

        return GetUnit(organisationalUnit.Parent);
    }

9 ответов


тут корень всегда есть!--1-->? Пробовал проверить

if (organisationalUnit.Parent == organisationalUnit)
    return null;

?


  1. у вас может быть единица, которая является собственным родителем, или за исключением того, что у вас может быть цикл родителей (например, A-B-C-A). То есть проблема может быть с данными, а не с кодом как таковым.
  2. убедитесь, что добытчики IsMainUnit и Parent Не называй GetUnit.

вы можете попробовать это, чтобы отладить его лучше. Это не повлияет на ваш производственный код.

using System.Diagnostics;

private Unit GetUnit(Unit organisationalUnit, int depth)
{
    debug.assert(depth < 10, "Reached an unexpected high recursion depth"); 

    if (organisationalUnit.IsMainUnit)
    {
        return organisationalUnit;                
    }

    if (organisationalUnit.Parent == null)
        return null;

    return GetUnit(organisationalUnit.Parent, depth + 1);
}

private Unit GetUnit(Unit organisationalUnit)
{
    return GetUnit(organisationalUnit.Parent, 0);
}

на второй мысли...

наиболее вероятно, что у вас есть круговая ссылка где-то.

A.parent = B;
B.parent = C;
C.parent = A;

вы можете попробовать передать набор предыдущих посещенных узлов и проверить, посещали ли вы этот узел раньше.

С рекурсия что ты должны быть уверены, что это будет конец и снят круговая ссылка-это ситуация, когда она не закончится.


есть ли причина не перекодировать его как итерацию? Таким образом, было бы проще установить жесткий предел глубины организационного дерева, чтобы поймать плохие (циклические) данные.

Рекурсия-это весело, и оптимизация хвоста может даже сделать его эффективным, но здесь это похоже на большой молоток для небольшой проблемы.


Я мало знаю о visual studio. Но вы должны проверить на рецидив. например,

private Unit GetUnit(Unit organisationalUnit)
{
      GetUnit(organisationalUnit, new vector());
}

private Unit GetUnit(Unit organisationalUnit,vector x)
{
    if (organisationalUnit.IsMainUnit)
    {
        return organisationalUnit;                
    }

    if (organisationalUnit.Parent == null)
        return null;

    x.add(this);
    if(x.contains(organisationalUnit.Parent)) throw new Exception("recurrent parent");
    return GetUnit(organisationalUnit.Parent,x);
}

один из способов сделать это-уменьшить размер стека, чтобы он мог разбиться раньше.

вы могли бы сделать это с помощью тратить кадры в начале программы, т. е. получить трассировку стека, такую как: f_n,f_(n-1),...,f_1, отходы, отходы,..., отходы, как в (в псевдо-коде C)

int wasted = 1;

отходы (int n, void (*f)()) { если (n > 0) отходы (n-1, f) еще f (); впустую += 1; }

main () { waste (N, mainprime);}

где mainprime - ваш старый main, и N достаточно большой, чтобы достичь f_1, который вы хотите.


код выглядит нормально, может быть, у вас есть несколько замкнутых циклов в дереве единиц? Попробуйте добавить линии трассировки с помощью organisationalUnit.Значения GetHashCode, выходные данные трассировки не ограничены и могут помочь определить причину переполнения стека.


Я думаю, вы могли бы избежать рекурсии, переписав это вдоль линий

while(organisationalUnit!=null && !organisationalUnit.IsMainUnit)
  organisationalUnit=organisationalUnit.Parent;

return organisationalUnit;

надеюсь, это поможет.

EDIT: я только что понял, что это все равно не удастся, если у вас есть какая-то циклическая зависимость.


у вас может быть цикл на графике (см., Это не дерево).

вы можете использовать такой код для его обнаружения:

private Unit GetUnit(Unit organisationalUnit) {
  return GetUnit(organisationalUnit, new HashSet<Unit>());
}

private Unit GetUnit(Unit organisationalUnit, HashSet<Unit> visited) {
  if (visited.Contains(organisationalUnit)) {
    throw new Exception("Cycle detected!"); // or just return null if you prefer
  }
  visited.Add(organisationalUnit);

  if (organisationalUnit.IsMainUnit) {
    return organisationalUnit;
  }

  if (organisationalUnit.Parent == null)
    return null;

  return GetUnit(organisationalUnit.Parent, visited);
}