Jak poradzić sobie lub uniknąć przepełnienia stosu w C++

W C++ przepełnienie stosu zwykle prowadzi do nieodwracalnej awarii programu. W przypadku programów, które muszą być naprawdę solidne, jest to niedopuszczalne zachowanie, szczególnie dlatego, że rozmiar stosu jest ograniczony. Kilka pytań, jak poradzić sobie z problemem.

  1. Czy istnieje sposób, aby zapobiec przepełnieniu stosu przez ogólną technikę. (Skalowalne, solidne rozwiązanie, które obejmuje radzenie sobie z zewnętrznymi bibliotekami zjadającymi dużo stosu itp.)

  2. Czy jest sposób na przepełnienia stosu w przypadku ich wystąpienia? Najlepiej, aby stos został rozwinięty, dopóki nie znajdzie się opiekun, który poradzi sobie z takim problemem.

  3. Istnieją języki, które mają wątki z rozszerzalnymi stosami. Czy coś takiego jest możliwe w C++?

Wszelkie inne pomocne uwagi na temat rozwiązania C++ będą mile widziane.

Author: Ralph Tandetzky, 2012-08-27

5 answers

Obsługa przepełnienia stosu nie jest właściwym rozwiązaniem, zamiast tego musisz upewnić się, że twój program nie przepełnia stosu.

Nie przydzielaj dużych zmiennych na stosie (gdzie to, co jest "duże", zależy od programu). Upewnij się, że każdy algorytm rekurencyjny kończy się po znanej maksymalnej głębokości. Jeśli algorytm rekursywny może rekursować nieznaną liczbę razy lub dużą liczbę razy, albo Zarządzaj rekursją samodzielnie (utrzymując własny dynamicznie przydzielany stos) lub przekształcić algorytm rekurencyjny w równoważny algorytm iteracyjny

Program, który musi być "naprawdę solidny", nie będzie używał zewnętrznych lub zewnętrznych bibliotek, które " zjadają dużo stosu."


Zauważ, że niektóre platformy informują program o przepełnieniu stosu i zezwalają programowi na obsługę błędu. Na przykład w systemie Windows wyrzucany jest wyjątek. Ten wyjątek nie jest wyjątkiem C++, ale jest wyjątkiem asynchronicznym. Natomiast wyjątek C++ może tylko asynchroniczny wyjątek może zostać wyrzucony w dowolnym momencie podczas wykonywania programu. Jest to jednak oczekiwane, ponieważ przepełnienie stosu może wystąpić w dowolnym momencie: dowolne wywołanie funkcji lub alokacja stosu może przepełnić stos.

Problem polega na tym, że przepełnienie stosu może spowodować, że wyjątek asynchroniczny zostanie wyrzucony nawet z kodu, który nie powinien wyrzucać żadnych WYJĄTKÓW (np. z funkcji oznaczonych noexcept lub throw() W C++). Więc, nawet jeśli się tym zajmiesz wyjątek jakoś, nie masz sposobu, aby wiedzieć, że twój program jest w bezpiecznym stanie. Dlatego najlepszym sposobem obsługi wyjątku asynchronicznego jest nie obsługiwanie go w ogóle(*). Jeśli jeden zostanie wyrzucony, oznacza to, że program zawiera błąd.

Inne platformy mogą mieć podobne metody "obsługi" błędu przepełnienia stosu, ale wszystkie takie metody mogą cierpieć na ten sam problem: kod, który nie spowoduje błędu, może spowodować błąd.

(*) istnieją kilka bardzo rzadkich WYJĄTKÓW.

 22
Author: James McNellis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 17:38:36

Możesz chronić przed przepełnieniem stosu za pomocą dobrych praktyk programistycznych, takich jak:

  1. Bądź bardzo ostrożny z rekurencją, ostatnio widziałem tak wynikającą z źle napisanej rekurencyjnej funkcji CreateDirectory, jeśli nie jesteś pewien, czy Twój kod jest w 100% ok, dodaj zmienną, która zatrzyma wykonywanie po N wywołań rekurencyjnych. Albo nawet lepiej nie pisać funkcji rekurencyjnych.
  2. nie twórz wielkich tablic na stosie, może to być tablica ukryta, jak bardzo duża tablica jako pole klasowe. Zawsze lepiej używać vector.
  3. Bądź bardzo ostrożny z alloca, zwłaszcza jeśli jest on umieszczony w jakiejś definicji makro. Widziałem wiele tak wynikających z konwersji łańcuchów makr umieszczonych w pętlach for, które używały alloca do szybkich alokacji pamięci.
  4. Upewnij się, że rozmiar stosu jest optymalny, jest to ważniejsze w platformach osadzonych. Jeśli wątek nie robi wiele, a następnie dać mu mały stos, w przeciwnym razie użyj większe. Wiem, że rezerwacja powinna mieć tylko jakiś adres zasięg-Nie pamięć fizyczna.

To są najbardziej tak przyczyny, które widziałem w ciągu ostatnich kilku lat.

Do automatycznego wyszukiwania więc powinieneś być w stanie znaleźć jakieś statyczne narzędzia do analizy kodu.

 6
Author: marcinj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 17:42:21

C++ jest potężnym językiem, a z tą mocą przychodzi możliwość strzelania sobie w stopę. Nie znam żadnego przenośnego mechanizmu do wykrywania i korygowania / przerywania, gdy występuje przepełnienie stosu. Z pewnością każde takie wykrycie byłoby specyficzne dla implementacji. Na przykład g++ dostarcza -fstack-protector do monitorowania użycia stosu.

Ogólnie najlepiej jest być proaktywnym w unikaniu dużych zmiennych opartych na stosie i ostrożnym z wywołaniami rekurencyjnymi.

 2
Author: Mark B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 17:20:48

[1] Re: rozszerzalne stosy. Możesz dać sobie więcej miejsca na stosie z czymś takim:

#include <iostream>

int main()
{
    int sp=0;

    // you probably want this a lot larger
    int *mystack = new int[64*1024];
    int *top = (mystack + 64*1024);

    // Save SP and set SP to our newly created
    // stack frame
    __asm__ ( 
        "mov %%esp,%%eax; mov %%ebx,%%esp":
        "=a"(sp)
        :"b"(top)
        :
        );
    std::cout << "sp=" << sp << std::endl;

    // call bad code here

    // restore old SP so we can return to OS
    __asm__(
        "mov %%eax,%%esp":
        :
        "a"(sp)
        :);

    std::cout << "Done." << std::endl;

    delete [] mystack;
    return 0;
}

Jest to składnia asemblera gcc.

 1
Author: Nathan Andrew Mullenax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-27 18:02:33
#include <iostream>
using **namespace** std;
class Complex
{

 public: double *re, *im;

 Complex()
 {

    re = new double(r);

    im = new double(m);

}

Complex( )
{

    re = new double; 
    im = new double;
    *re = *t.re; 
    *im= *t.im;

}

~Complex()
{

        delete re, im;

}

};

int main() {

double x, y, z;
cin >> x >> y  >> z;
Complex n1(x,y);
cout << *n1.re << "+" << *n1.im << "i ";
Complex n2 = n1;
cout << *n2.re << "+" << *n2.im << "i ";
*n1.im = z;
cout << *n2.re << "+" << *n2.im << "i ";
cout << *n1.re << "+" << *n1.im << "i ";
return 0;
}
 -2
Author: suman das,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-15 06:02:27