C # rileva un'eccezione di overflow dello stack


115

Ho una chiamata ricorsiva a un metodo che genera un'eccezione di overflow dello stack. La prima chiamata è circondata da un blocco try catch ma l'eccezione non viene rilevata.

L'eccezione di overflow dello stack si comporta in modo speciale? Posso rilevare / gestire correttamente l'eccezione?

Non sono sicuro se pertinente, ma informazioni aggiuntive:

  • l'eccezione non viene generata nel thread principale

  • l'oggetto in cui il codice genera l'eccezione viene caricato manualmente da Assembly.LoadFrom (...). CreateInstance (...)


3
@RichardOD, sicuramente ho corretto il bug perché era un bug. Tuttavia il problema può apparire in un modo diverso e voglio gestirlo
Toto

7
D'accordo, uno stack overflow è un errore grave che non può essere rilevato perché non dovrebbe essere rilevato. Correggi invece il codice rotto.
Ian Kemp

11
@RichardOD: Se si desidera progettare ad esempio un parser a discesa ricorsiva e non imporre limiti artificiali alla profondità oltre quelli effettivamente richiesti dalla macchina host, come si dovrebbe procedere? Se avessi i miei druthers, ci sarebbe un'eccezione StackCritical che potrebbe essere catturata in modo esplicito, che verrebbe lanciata mentre c'era ancora un piccolo spazio di stack rimasto; si disabilitava fino a quando non veniva effettivamente lanciato e quindi non poteva essere catturato fino a quando non rimaneva una quantità sicura di spazio nello stack.
supercat

2
Questa domanda è utile: voglio fallire uno unit test se si verifica un'eccezione di overflow dello stack, ma NUnit sposta semplicemente il test nella categoria "ignorato" invece di fallirlo come farebbe con altre eccezioni - devo prenderlo e fai un Assert.Failinvece. Quindi seriamente: come lo facciamo?
BrainSlugs83

Risposte:


109

A partire dalla 2.0, un'eccezione StackOverflow può essere rilevata solo nelle seguenti circostanze.

  1. Il CLR viene eseguito in un ambiente ospitato * in cui l'host consente specificamente di gestire le eccezioni StackOverflow
  2. L'eccezione stackoverflow viene generata dal codice utente e non a causa di una situazione di overflow dello stack effettiva ( riferimento )

* "ambiente ospitato" come in "il mio codice ospita CLR e configuro le opzioni di CLR" e non "il mio codice viene eseguito su hosting condiviso"


27
Se non può essere catturato in uno scebario pertinente, perché esiste l'oggetto StackoverflowException?
Manu,

9
@Manu per almeno un paio di motivi. 1) È che potrebbe essere catturato, più o meno, in 1.1 e quindi aveva uno scopo. 2) Può ancora essere catturato se stai ospitando il CLR, quindi è ancora un tipo di eccezione valido
JaredPar

3
Se non può essere catturato ... Perché l'evento di Windows che spiega cosa è successo non include la traccia dello stack completo per impostazione predefinita?

10
Come si fa a consentire la gestione di StackOverflowExceptions in un ambiente ospitato? Il motivo per cui lo chiedo è perché gestisco un ambiente ospitato e ho questo problema esatto, in cui distrugge l'intero pool di app. Preferirei di gran lunga che interrompesse il thread, dove può tornare all'inizio, e posso quindi registrare l'errore e continuare senza che tutti i thread dell'apppool vengano uccisi.
Brain2000

Starting with 2.0 ..., Sono curioso, cosa impedisce loro di catturare SO e come è stato possibile 1.1(lo hai detto nel tuo commento)?
M.kazem Akhgary

47

Il modo giusto è riparare l'overflow, ma ...

Puoi darti uno stack più grande: -

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

Puoi utilizzare la proprietà System.Diagnostics.StackTrace FrameCount per contare i frame che hai utilizzato e generare la tua eccezione quando viene raggiunto un limite di frame.

Oppure puoi calcolare la dimensione dello stack rimanente e lanciare la tua eccezione quando scende al di sotto di una soglia: -

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();
    }
}

Prendi il formaggio. ;)


47
Cheeseè tutt'altro che specifico. Vorreithrow new CheeseException("Gouda");
C.Evenhuis

13
@ C.Evenhuis Anche se non c'è dubbio che il Gouda sia un formaggio eccezionale, dovrebbe essere una RollingCheeseException ("Double Gloucester") vedi davvero cheese-rolling.co.uk

3
lol, 1) non è possibile aggiustarlo perché senza prenderlo spesso non sai dove succede 2) aumentare lo Stacksize è inutile con ricorsione infinita e m 3) controllare lo Stack nella posizione giusta è come il primo
Firo

2
ma sono intollerante al lattosio
rifare il

39

Dalla pagina MSDN su StackOverflowException s:

Nelle versioni precedenti di .NET Framework, l'applicazione poteva rilevare un oggetto StackOverflowException (ad esempio, per ripristinare da ricorsione illimitata). Tuttavia, tale pratica è attualmente sconsigliata perché è necessario un codice aggiuntivo significativo per rilevare in modo affidabile un'eccezione di overflow dello stack e continuare l'esecuzione del programma.

A partire da .NET Framework versione 2.0, un oggetto StackOverflowException non può essere intercettato da un blocco try-catch e il processo corrispondente viene terminato per impostazione predefinita. Di conseguenza, si consiglia agli utenti di scrivere il proprio codice per rilevare e prevenire un overflow dello stack. Ad esempio, se l'applicazione dipende dalla ricorsione, utilizzare un contatore o una condizione di stato per terminare il ciclo ricorsivo. Si noti che un'applicazione che ospita il Common Language Runtime (CLR) può specificare che CLR scarica il dominio dell'applicazione in cui si verifica l'eccezione di overflow dello stack e lascia che il processo corrispondente continui. Per ulteriori informazioni, vedere Interfaccia ICLRPolicyManager e Hosting di Common Language Runtime.


23

Come hanno già detto diversi utenti, non puoi catturare l'eccezione. Tuttavia, se hai difficoltà a scoprire dove sta accadendo, potresti voler configurare Visual Studio in modo che si interrompa quando viene lanciato.

Per farlo, devi aprire le Impostazioni delle eccezioni dal menu "Debug". Nelle versioni precedenti di Visual Studio, si trova in "Debug" - "Eccezioni"; nelle versioni più recenti, si trova su "Debug" - "Windows" - "Impostazioni eccezioni".

Una volta aperte le impostazioni, espandere "Eccezioni Common Language Runtime", espandere "Sistema", scorrere verso il basso e selezionare "System.StackOverflowException". Quindi puoi guardare lo stack di chiamate e cercare lo schema ripetitivo delle chiamate. Questo dovrebbe darti un'idea di dove cercare per correggere il codice che causa l'overflow dello stack.


1
Dov'è il debug - Eccezioni in VS 2015?
FrenkyB

1
Debug - Windows - Impostazioni eccezioni
Simon

15

Come accennato in precedenza più volte, non è possibile catturare un'eccezione StackOverflowException che è stata generata dal sistema a causa dello stato del processo danneggiato. Ma c'è un modo per notare l'eccezione come un evento:

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

A partire dalla versione 4 di .NET Framework, questo evento non viene generato per le eccezioni che danneggiano lo stato del processo, come overflow dello stack o violazioni di accesso, a meno che il gestore eventi non sia critico per la sicurezza e abbia l'attributo HandleProcessCorructedStateExceptionsAttribute.

Tuttavia la tua applicazione terminerà dopo essere uscito dalla funzione evento (una soluzione MOLTO sporca, era riavviare l'app all'interno di questo evento haha, non l'ho fatto e non lo farà mai). Ma è abbastanza buono per la registrazione!

Nelle versioni di .NET Framework 1.0 e 1.1, un'eccezione non gestita che si verifica in un thread diverso dal thread dell'applicazione principale viene rilevata dal runtime e pertanto non causa la chiusura dell'applicazione. Pertanto, è possibile che l'evento UnhandledException venga generato senza che l'applicazione venga terminata. A partire da .NET Framework versione 2.0, questo backstop per le eccezioni non gestite nei thread figlio è stato rimosso, poiché l'effetto cumulativo di tali errori silenziosi includeva degrado delle prestazioni, dati danneggiati e blocchi, tutti difficili da eseguire il debug. Per ulteriori informazioni, incluso un elenco di casi in cui il runtime non viene terminato, vedere Eccezioni nei thread gestiti.


6

Sì da CLR 2.0 lo stack overflow è considerato una situazione non recuperabile. Quindi il runtime interrompe ancora il processo.

Per i dettagli, consultare la documentazione http://msdn.microsoft.com/en-us/library/system.stackoverflowexception.aspx


Da CLR 2.0 a StackOverflowExceptiontermina il processo per impostazione predefinita.
Brian Rasmussen

No. Puoi catturare OOM e in alcuni casi potrebbe avere senso farlo. Non so cosa intendi per filo che scompare. Se un thread ha un'eccezione non gestita, il CLR terminerà il processo. Se il tuo thread completa il suo metodo, verrà ripulito.
Brian Rasmussen

5

Non puoi. Il CLR non te lo permetterà. Un overflow dello stack è un errore irreversibile e non può essere ripristinato.


Quindi come si fa a far fallire uno Unit Test per questa eccezione se invece di essere catturabile, invece, si blocca il runner di unit test?
BrainSlugs83

1
@ BrainSlugs83. Non lo fai, perché è un'idea sciocca. Perché stai testando se il tuo codice fallisce comunque con un'eccezione StackOverflowException? Cosa succede se il CLR cambia in modo da poter gestire uno stack più profondo? Cosa succede se chiami la tua funzione unit testata da qualche parte che ha già uno stack profondamente annidato? Sembra qualcosa che non può essere testato. Se stai provando a lanciarlo manualmente, scegli un'eccezione migliore per l'attività.
Matthew Scharley

5

Non puoi come spiegano la maggior parte dei post, lasciami aggiungere un'altra area:

Su molti siti web troverai persone che dicono che il modo per evitarlo è usare un AppDomain diverso, quindi se ciò accade il dominio verrà scaricato. Questo è assolutamente sbagliato (a meno che tu non ospiti il ​​tuo CLR) poiché il comportamento predefinito del CLR genererà un evento KillProcess, abbattendo il tuo AppDomain predefinito.


3

È impossibile, e per una buona ragione (per esempio, pensa a tutte quelle catture (Eccezione) {} in giro).

Se vuoi continuare l'esecuzione dopo l'overflow dello stack, esegui codice pericoloso in un AppDomain diverso. I criteri CLR possono essere impostati per terminare l'AppDomain corrente in caso di overflow senza influire sul dominio originale.


2
Le istruzioni "catch" non sarebbero davvero un problema, poiché nel momento in cui un'istruzione catch potrebbe essere eseguita, il sistema avrebbe annullato gli effetti di qualsiasi cosa avesse tentato di utilizzare due molto spazio di stack. Non c'è motivo per cui il rilevamento delle eccezioni di overflow dello stack debba essere pericoloso. Il motivo per cui tali eccezioni non possono essere catturate è che consentire loro di essere catturati in modo sicuro richiederebbe l'aggiunta di un sovraccarico extra a tutto il codice che utilizza lo stack, anche se non trabocca.
supercat

4
Ad un certo punto l'affermazione non è ben pensata. Se non riesci a catturare Stackoverflow, forse non sai mai DOVE è successo in un ambiente di produzione.
Offler
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.