2009-10-17 11 views

Odpowiedz

12

Jeśli zadzwonisz wystarczająco metod, przepełnienie stos może wystąpić w dowolnym momencie. Chociaż, jeśli otrzymasz błędy przepełnienia stosu bez użycia rekursji, możesz przemyśleć, jak sobie radzisz. Po rekursji jest to tak proste, ponieważ w nieskończonej pętli wywołujesz mnóstwo metod.

16

Zadeklaruj tablicę ENORMOUS jako zmienną lokalną.

+0

Większość kompilatorów po prostu nie skompiluje tego. To nie jest przepełnienie stosu. –

+1

@Chris - Nie skompiluje? Myślałem, że maksymalny rozmiar stosu został ustawiony przez linker i nie jest znany kompilatorowi. – ChrisW

+2

kompilator nie może go przechwycić, chyba że kompilator jest w stanie przeanalizować kod dla przewidywanego użycia stosu w czasie wykonywania, co może być niezwykle trudne. – JustJeff

2

Każde wywołanie metody, które jeszcze nie zwróciło, zużywa trochę miejsca na stosie. (Metody z większą ilością zmiennych lokalnych zajmują więcej miejsca). Bardzo głęboki stos wywołań może spowodować przepełnienie stosu.

Należy pamiętać, że w systemach z ograniczoną pamięcią (urządzenia mobilne i tym podobne) nie ma zbyt dużo miejsca na stosie i szybciej się wyczerpią.

+1

Pracowałem nad jednym projektem konsoli, w którym nasze procesy miały stosy 32K. W jednej z procedur istniały dwie macierze 16K. Chociaż użycie tablic było wyłączne i nie były one w tym samym zakresie, kompilator nadal przydzielił 32K przestrzeni stosu i przepełnił nasz stos (teoretycznie mądrzejszy kompilator miałby zarezerwowane tylko 16K). Zmieniłem je, aby przydzielić/za darmo, aby rozwiązać problem. – Adisak

20

Ponieważ nikt inny nie wspomniał:

throw new System.StackOverflowException(); 

Można to robić przy testowaniu lub usterek zrobić zastrzyk.

+1

Świetnie - zakładając, że używasz .NET =) –

1

Jeśli mówimy o C++ z rozsądnym biblioteki standardowej, I obraz, że to działa:

while (true) { 
    alloca(1024 * 1024); // arbitrary - 1M per iteration. 
} 

Szczegóły dotyczące alloca.

+0

Czy jest to wyjątek ze stackoverflow lub z pamięci? – Juliet

+2

@juliet: Funkcja alloca() przydziela miejsce w ramce stosu osoby wywołującej. – pierrotlefou

+2

Nie powinno to powodować wyjątku, jeśli funkcja alloca() jest poprawnie zaimplementowana. Wywołanie alloca() ma zwrócić wartość NULL, jeśli brakuje miejsca na stosie, zamiast zgłaszać wyjątek. To, co powinno się stać, to, że po wyczerpaniu przestrzeni stosu, twój kod utknie w nieskończonej pętli alloca(), która zwraca NULL. – Adisak

2

Krótka odpowiedź: jeśli masz obiekt, który wywołuje obiekt wewnętrzny, zwiększasz ślad stosu o 1. Tak więc, jeśli masz 1000 obiektów zagnieżdżonych w sobie nawzajem, każdy wywołując swój obiekt wewnętrzny, w końcu otrzymasz przepełnienie stosu.

Oto demonstracja sposobu generowania liczb pierwszych użyciu zagnieżdżonych iteratorów:

using System; 
using System.Collections.Generic; 
using System.Text; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Program p = new Program(); 

      IEnumerator<int> primes = p.AllPrimes().GetEnumerator(); 
      int numberOfPrimes = 1000; 
      for (int i = 0; i <= numberOfPrimes; i++) 
      { 
       primes.MoveNext(); 
       if (i % 1000 == 0) 
       { 
        Console.WriteLine(primes.Current); 
       } 
      } 
      Console.ReadKey(true); 
     } 

     IEnumerable<int> FilterDivisors(IEnumerator<int> seq, int num) 
     { 
      while (true) 
      { 
       int current = seq.Current; 
       if (current % num != 0) 
       { 
        yield return current; 
       } 
       seq.MoveNext(); 
      } 
     } 

     IEnumerable<int> AllIntegers() 
     { 
      int i = 2; 
      while (true) 
      { 
       yield return i++; 
      } 
     } 

     IEnumerable<int> AllPrimes() 
     { 
      IEnumerator<int> nums = AllIntegers().GetEnumerator(); 
      while (true) 
      { 
       nums.MoveNext(); 
       int prime = nums.Current; 
       yield return prime; 

       // nested iterator makes a big boom  
       nums = FilterDivisors(nums, prime).GetEnumerator(); 
      } 
     } 
    } 
} 

Nie ma rekursji, ale program wygeneruje wyjątek przepełnienia stosu po około 150.000 liczb pierwszych.

+0

kod ncie, przypomina mi ewolucję programisty Haskella :) (ton kodu przeciwko onelinerowi - programowanie silni) –

7

Następujące informacje dotyczą systemu Windows, ale większość systemów operacyjnych implementuje je w podobny sposób.

Krótka odpowiedź brzmi: jeśli dotkniesz ostatniej strony straży, zostanie rzucona.

Wyjątek typu EXCEPTION_STACK_OVERFLOW (C00000FD) jest zgłaszany, gdy aplikacja dotknie dolnej strony stosu, która jest oznaczona flagą ochronną PAGE_GUARD, i nie ma miejsca na wzrost stosu (zatwierdzenie jeszcze jednej strony), patrz How to trap stack overflow in a Visual C++ application.
Typowy przypadek, kiedy tak się dzieje, to sytuacja, gdy stos urósł w wyniku wielu ramek funkcyjnych na stosie (tj. Poza rekursją sterowania), w wyniku mniejszej liczby klatek, ale bardzo dużych rozmiarów klatek (funkcje o bardzo dużym rozmiarze obiekt o zasięgu lokalnym) lub przez jawne przydzielanie ze stosu przy pomocy _alloca.
Innym sposobem na spowodowanie wyjątku jest celowe dotknięcie strony strażnika, np. przez wyłuskiwanie wskaźnika wskazującego na tę stronę. Może się to zdarzyć z powodu błędu zmiennej inicjalizacji.

Przepełnienia stosu mogą wystąpić na poprawnych ścieżkach wykonania, jeśli dane wejściowe powodują bardzo głęboki poziom zagnieżdżenia.Na przykład zobaczyć Stack overflow occurs when you run a query that contains a large number of arguments inside an IN or a NOT IN clause in SQL Server.

-1

Najprostszym sposobem dokonania StackOverflowException jest następujący:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      SomeClass instance = new SomeClass(); 
      string name = instance.Name; 
     } 
    } 

    public class SomeClass 
    { 
     public string Name 
     { 
      get 
      { 
       return Name; 
      } 
     } 
    } 
} 
+1

W rzeczywistości jest to drugi najprostszy sposób po prostu rzucając wyjątek :) –

+3

Pytanie specjalnie wykluczone rekursji. – meriton

+0

Masz rację, mój błąd. –

0
int main() 
{ 
    //something on the stack 
    int foo = 0; 
    for (
    //pointer to an address on the stack 
    int* p = &foo; 
    //forever 
    ; 
    //ever lower on the stack (assuming that the stack grows downwards) 
    --p) 
    { 
    //write to the stack 
    *p = 42; 
    } 
} 
Powiązane problemy