Может ли переполнение стека произойти по какой-либо другой причине этой рекурсии? - PullRequest
4 голосов
/ 23 января 2012

Я получаю исключение переполнения стека для сегмента кода, который, кажется, не в состоянии произвести переполнение стека ... Это выглядит так:

public String WriteToFile(XmlDocument pDoc, String pPath)
{
  string source = "";
  string seq = "";
  string sourcenet = "";

  XmlNodelist sourceNode = pDoc.GetElementsByTagName(XmlUtils.Nodes.Source);
  source = sourceNode.Item(0).InnerText;

  XmlNodelist sqList= pDoc.GetElementsByTagName(XmlUtils.Nodes.Seq);
  seq = sqList.Item(0).InnerText;

  XmlNodelist sourceNets = pDoc.GetElementsByTagName(XmlUtils.Nodes.SourceNets);
  sourcenet = sourceNets.Item(0).InnerText;

  string fileName = Folders.GetMyFileName(source, seq, sourcenet);
  string fullPath = Path.Combine(pPath, fileName);

  pDoc.Save(pFullPathFile);  <--- Stackoverflow is raised here

  return pFullPathFile; 
}

Нет рекурсивных вызовов, если вы исследуете стек вызовов, он имеет глубину 2, прежде чем перейти к «внешнему коду» (я предполагаю, что это не та внешняя часть, а часть инфраструктуры, которая запускает поток, у которого отключена отладка).

¿Есть ли в любом случае исключение, которое может быть вызвано, потому что что-то кроме рекурсивного вызова?Он ВСЕГДА терпит неудачу при вызове метода pDoc.Save ... и pDoc на самом деле не такой большой ... больше как 32 КБ данных ...

Ответы [ 4 ]

7 голосов
/ 23 января 2012

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

  • Наличие глубоко вложенного стека, который не является рекурсивным. Подумайте о штормах событий, когда событие A приводит к событию B, которое приводит к событию C, у каждого из которых есть обработчики, которые глубоко увеличивают стек.
  • Наличие мелкого стека, которое возникает после некоторых больших выделений стека
4 голосов
/ 23 января 2012

Переполнение стека просто означает, что вы исчерпали стек, это не должно быть вызвано рекурсией.Конечно, поскольку рекурсия использует стек, она часто является причиной исключения переполнения стека, но это не обязательно.

При этом, учитывая предоставленную вами информацию, она не звучиткак будто должно быть что-либо, вызывающее переполнение стека в предоставленном вами коде.

Потоки в C # по умолчанию имеют стек 1 МБ, но вы можете создать новый поток с меньшим стеком .Вы сами создаете потоки в этой программе и устанавливаете размер стека?

Кроме того, посмотрите на раздел внешнего кода (щелкните правой кнопкой мыши там, где написано External Code в окне Call Stack, выберите «Показать»).внешний код ").Посмотрите, выглядит ли что-то неправильно, проходит ли фреймворк по многим причинам много вызовов методов для сохранения?

2 голосов
/ 23 января 2012

Действительно рекурсивный вызов.

pDoc.Save() вызывает WriteTo(XmlWriter w) на документе, который вызывает WriteContentTo(XmlWriter w).

Затем вызывается WriteTo(XmlWriter w) на всех узлах корневого уровня, который будет содержать один узел элемента (возможно, также некоторые комментарии, пробелы, инструкции по обработке, декларацию документа ...).

На этом элементе это заставит его написать свой тег ('<', имя элемента, а затем любые атрибуты) с последующим вызовом <code>WriteContentTo(XmlWriter w), который вызывает WriteTo(XmlWriter w) для каждого дочернего элемента, который вызывает WriteContentTo(XmlWriter w), и так далее и тому подобное.

Следовательно, это действительно рекурсивно в том, что каждый элемент вызывает один и тот же метод для своих дочерних элементов и с достаточно глубоким документом на достаточно маленьком пространстве стека (по умолчанию 1 МБ в большинстве приложений, но 256 КБ в ASP.NET), вы ' будет переполнение стека.

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

Если у вас возникли проблемы из-за этой рекурсии, помните, что реализация WriteTo по сути (вручную вставляя в нее WriteContentTo):

w.WriteStartElement(this.Prefix, this.LocalName, this.NamespaceURI);
if (this.HasAttributes)
{
    XmlAttributeCollection attributes = this.Attributes;
    for (int i = 0; i < attributes.Count; i++)
    {
        attributes[i].WriteTo(w);
    }
}
if (this.IsEmpty)
{
    w.WriteEndElement();
}
else
{
  for (XmlNode node = this.FirstChild; node != null; node = node.NextSibling)
  {
    node.WriteTo(w);
  }
    w.WriteFullEndElement();
}

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

0 голосов
/ 23 января 2012

В некоторых языках / средах выполнения переполнение стека может происходить из-за больших выделений памяти, которые не связаны с самим стеком вызовов. Вполне возможно, что «внешний код» (я предполагаю, что фреймворк) работает либо в этой ситуации, либо на самом деле имеет классическую проблему переполнения рекурсии, которую вы не видите, потому что вы не можете обязательно отлаживать ее.

...