Виключення виключення переповнення стека


115

У мене є рекурсивний виклик методу, який викидає виняток переповнення стека. Перший виклик оточений блоком спробу лову, але виняток не вловлюється.

Чи поводиться виняток переповнення стека особливим чином? Чи можу я зловити / обробити виняток належним чином?

Не впевнений у відповідності, але додаткова інформація:

  • виняток не закидається в основну нитку

  • об’єкт, куди викидає код винятку, завантажується вручну в Assembly.LoadFrom (...). CreateInstance (...)


3
@RichardOD, впевнений, що я виправляю помилку, оскільки це була помилка. Однак проблема може з’явитися по-іншому, і я хочу вирішити її
Toto

7
Погоджено, переповнення стека - це серйозна помилка, яку неможливо впізнати, оскільки її не слід виловлювати. Виправити зламаний код замість цього.
Ян Кемп

11
@ RichardOD: Якщо хочеться сконструювати, наприклад, аналізатор рекурсивного спуску, а не накладати штучні межі на глибину, що перевищує ті, які фактично вимагає хост-машина, то як слід це робити? Якби я мав свої барабани, був би виняток StackCritical, який можна було б чітко зафіксувати, і який був би звільнений, поки залишилось небагато місця для стека; він би відключив себе, поки не був фактично кинутий, і потім не міг бути спійманий, поки не залишиться безпечної кількості місця у стопці.
supercat

2
Це питання є корисним - я хочу провалити тест одиниці, якщо трапляється виняток переповнення стека - але NUnit просто переміщує тест до категорії "ігнорується", а не відмовляє, як це було б за інших винятків - мені потрібно це злавити і робити Assert.Failзамість цього. Так серйозно - як ми з цим підемо?
BrainSlugs83

Відповіді:


109

Починаючи з 2.0, виняток StackOverflow може бути спійманий лише у наступних обставинах.

  1. CLR запускається в розміщеному середовищі *, де хост спеціально дозволяє обробляти винятки StackOverflow
  2. Виняток stackoverflow викидається за кодом користувача, а не через фактичну ситуацію переповнення стека ( Довідка )

* "розміщене середовище", як у "мій код хостить CLR, і я налаштовую параметри CLR", а не "мій код працює на спільному хостингу"


27
Якщо він не може бути спійманий у будь-якому відповідному сцебаріо, чому існує об'єкт StackoverflowException?
Ману

9
@Manu хоча б з кількох причин. 1) Хіба що це може бути зловлене, начебто, в 1.1 і, отже, має мету. 2) Він все ще може бути спійманий, якщо ви розміщуєте CLR, тому він все ще є дійсним типом винятків
JaredPar

3
Якщо його неможливо зловити ... Чому подія Windows, що пояснює те, що сталося, не включає повний слід стека за замовчуванням?

10
Як можна дозволити обробці StackOverflowExceptions в розміщеному середовищі? Причина, про яку я прошу, полягає в тому, що я запускаю розміщене середовище, і у мене виникає саме ця проблема, коли це знищує весь пул додатків. Я б швидше запропонував їй перервати нитку, де вона може розмотатися назад до верху, і я можу зафіксувати помилку та продовжувати роботу, не вбиваючи всіх потоків програми.
Brain2000

Starting with 2.0 ..., Мені цікаво, що заважає їм зловити ТАК і як це було можливо 1.1(ви це згадали у своєму коментарі)?
M.kazem Akhgary

47

Правильний спосіб - це виправити перелив, але….

Ви можете надати собі більший стек: -

using System.Threading;
Thread T = new Thread(threadDelegate, stackSizeInBytes);
T.Start();

Ви можете використовувати властивість System.Diagnostics.StackTrace FrameCount для підрахунку використовуваних кадрів та викинути власне виключення, коли буде досягнуто обмеження кадру.

Або ви можете обчислити розмір стека, що залишився, і викинути власний виняток, коли він опуститься нижче порогу: -

class Program
{
    static int n;
    static int topOfStack;
    const int stackSize = 1000000; // Default?

    // The func is 76 bytes, but we need space to unwind the exception.
    const int spaceRequired = 18*1024; 

    unsafe static void Main(string[] args)
    {
        int var;
        topOfStack = (int)&var;

        n=0;
        recurse();
    }

    unsafe static void recurse()
    {
        int remaining;
        remaining = stackSize - (topOfStack - (int)&remaining);
        if (remaining < spaceRequired)
            throw new Exception("Cheese");
        n++;
        recurse();
    }
}

Просто ловіть Сир. ;)


47
Cheeseдалеко не конкретне. Я б поїхавthrow new CheeseException("Gouda");
C.Evenhuis

13
@ C.Evenhuis Хоча немає сумнівів у тому, що Gouda - винятковий сир, він повинен бути RollingCheeseException ("Подвійний Глостер") дійсно бачити cheese-rolling.co.uk

3
lol, 1) виправити це неможливо, тому що без його лову ви часто не знаєте, де це відбувається 2) збільшення розміру Stacks марно без нескінченних рекурсій іm 3) перевірка стека в потрібному місці подібна до першого
Firo

2
але я непереносимий лактозою
повтор

39

На сторінці MSDN на StackOverflowException s:

У попередніх версіях .NET Framework ваше додаток може зафіксувати об'єкт StackOverflowException (наприклад, відновити після безмежної рекурсії). Однак ця практика наразі не рекомендується, оскільки необхідний значний додатковий код для надійного вилучення виключення переповнення стека та продовження виконання програми.

Починаючи з .NET Framework версії 2.0, об'єкт StackOverflowException не може бути зафіксований блоком спробу вловлювання, і відповідний процес закінчується за замовчуванням. Отже, користувачам рекомендується написати свій код, щоб виявити та запобігти переповненню стека. Наприклад, якщо ваша програма залежить від рекурсії, використовуйте лічильник або стан стану, щоб припинити рекурсивний цикл. Зауважте, що програма, в якій розміщено загальну мову виконання (CLR), може вказати, що CLR вивантажує домен програми, де відбувається виняток переповнення стека, і дозволить продовжувати відповідний процес. Для отримання додаткової інформації див. Інтерфейс ICLRPolicyManager та розміщення загальної мови виконання.


23

Як уже сказали кілька користувачів, ви не можете зловити виняток. Однак якщо ви намагаєтеся дізнатися, де це відбувається, можливо, ви захочете налаштувати візуальну студію, щоб вона зламалася, коли її кинули.

Для цього вам потрібно відкрити Параметри винятку в меню "Налагодження". У більш старих версіях Visual Studio це в "Налагодження" - "Винятки"; у нових версіях він знаходиться в розділі "Налагодження" - "Windows" - "Налаштування виключення".

Після відкриття налаштувань розгорніть "Винятки з загальної мови виконання", розгорніть "Система", прокрутіть униз і поставте прапорець "System.StackOverflowException". Потім ви можете переглянути стек викликів і шукати повторюваний зразок дзвінків. Це повинно дати вам уявлення, де шукати, щоб виправити код, що викликає переповнення стека.


1
Де налагодження - винятки у VS 2015?
FrenkyB

1
Налагодження - Windows - Налаштування
Саймон

15

Як було сказано вище кілька разів, неможливо зловити StackOverflowException, який підняв Система через пошкоджений стан процесу. Але є спосіб помітити виняток як подію:

http://msdn.microsoft.com/en-us/library/system.appdomain.unhandledexception.aspx

Починаючи з версії 4 .NET Framework, ця подія не піднімається за винятками, які пошкоджують стан процесу, наприклад, переповнення стека чи порушення доступу, якщо тільки обробник події не є критичним для безпеки та має атрибут HandleProcessCorruptedStateExceptionsAttribute.

Тим не менш, ваш додаток буде припинено після виходу з функції події (ДУЖЕ брудне вирішення проблеми - це перезапустити програму в рамках цієї події. Але це досить добре для ведення лісу!

У версіях .NET Framework версій 1.0 та 1.1 нерозроблений виняток, що виникає в потоці, відмінному від основного потоку програми, потрапляє під час виконання програми і тому не призводить до припинення програми. Таким чином, можливе виникнення події UnhandledException без завершення роботи програми. Починаючи з .NET Framework версії 2.0, цю резервну зупинку для неупорядкованих винятків у дочірніх потоках було видалено, оскільки сукупний ефект таких мовчазних збоїв включав погіршення продуктивності, пошкоджені дані та блокування, все це було важко налагодити. Для отримання додаткової інформації, включаючи перелік випадків, коли час виконання не закінчується, див. Винятки в керованих потоках.


6

Так, переповнення стека CLR 2.0 вважається ситуацією, що не підлягає відновленню. Тож час роботи все-таки припиняє процес.

Докладніше див. У документації http://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx


З CLR 2.0 a StackOverflowExceptionза замовчуванням припиняє процес.
Брайан Расмуссен

Ні. Ви можете зловити OOM, а в деяких випадках це може мати сенс. Я не знаю, що ти маєш на увазі, як нитка зникає. Якщо в потоці є необроблений виняток, CLR припиняє процес. Якщо ваша нитка завершить свій метод, вона буде очищена.
Брайан Расмуссен

5

Ви не можете. CLR не дозволить вам. Переповнення стека - це фатальна помилка, яку неможливо відновити.


Отже, як зробити так, щоб тест модуля не вдався до цього винятку, якщо замість того, щоб його можна було знайти, він замінить тестовий бігун одиниці?
BrainSlugs83

1
@ BrainSlugs83. Не знаєш, бо це дурна думка. Чому ви тестуєте, якщо ваш код все-таки не працює з StackOverflowException? Що станеться, якщо CLR зміниться, щоб він міг обробляти глибший стек? Що станеться, якщо ви викликаєте тестувану функцію свого пристрою десь, де вже є глибоко вкладений стек? Здається, щось, що не вдається перевірити. Якщо ви намагаєтеся кинути це вручну, виберіть кращий виняток для завдання.
Меттью Шарлі

5

Ви не можете, як пояснює більшість публікацій, дозвольте мені додати ще одну область:

На багатьох веб-сайтах ви знайдете людей, які говорять, що спосіб уникнути цього - використання іншого AppDomain, тому, якщо це станеться, домен буде завантажений. Це абсолютно неправильно (якщо ви не приймаєте CLR), оскільки поведінка CLR за замовчуванням призведе до події KillProcess, знизивши ваш AppDomain за замовчуванням.


3

Це неможливо, і з поважної причини (для одного, подумайте про весь цей вилов (Виняток) {} навколо).

Якщо ви хочете продовжити виконання після переповнення стека, запустіть небезпечний код в іншій AppDomain. Політику CLR можна встановити для припинення поточного AppDomain при переповненні, не впливаючи на вихідний домен.


2
Висловлювання "catch" насправді не були б проблемою, оскільки до того моменту, коли заява на виловку може виконати систему, вона відкинула б ефекти, що намагалися використовувати два великих місця у стеку. Немає жодних причин вилучення винятків переповнення стека повинно бути небезпечним. Причина, за якою такі винятки неможливо спіймати, полягає в тому, що для дозволу їх зловити потрібно вимагати додавання додаткових накладних витрат до всього коду, який використовує стек, навіть якщо він не переповнюється.
supercat

4
У якийсь момент твердження недостатньо продумане. Якщо ви не можете зловити Stackoverflow, ви, можливо, ніколи не знаєте, де це сталося у виробничих умовах.
Розпродаж
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.