Как обрабатывать или избегать переполнения стека в C++

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

  1. есть ли способ предотвратить переполнение стека общей техникой. (Масштабируемое, надежное решение, которое включает в себя работу с внешними библиотеками, потребляющими много стека, так далее.)

  2. есть ли способ обрабатывать переполнения стека в случае их возникновения? Предпочтительно, стек разматывается, пока не появится обработчик для решения этой проблемы.

  3. есть языки, которые имеют потоки с расширяемыми стеками. Возможно ли что-то подобное в C++?

любые другие полезные комментарии по решению поведения C++ будут оценены.

5 ответов


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

не выделяйте большие переменные в стеке (где "большое" зависит от программы). Убедитесь, что любой рекурсивный алгоритм завершается после известной максимальной глубины. Если рекурсивный алгоритм может рекурсировать неизвестное количество раз или большое количество раз, либо управляйте рекурсией самостоятельно (сохраняя свой собственный динамически выделенный stack) или преобразовать рекурсивный алгоритм в эквивалентный итерационный алгоритм

программа, которая должна быть "действительно надежной", не будет использовать сторонние или внешние библиотеки, которые " съедают много стека."


обратите внимание, что некоторые платформы уведомляют программу при переполнении стека и позволяют программе обрабатывать ошибку. В Windows, например, исключение. Это исключение не является исключением C++, хотя это асинхронное исключение. А Исключение C++ может быть вызвано только throw оператор, асинхронное исключение может быть вызвано в любое время во время выполнения программы. Это ожидается, однако, потому что переполнение стека может произойти в любое время: любой вызов функции или распределение стека может переполнить стек.

проблема в том, что переполнение стека может привести к асинхронному исключению даже из кода, который не должен вызывать никаких исключений (например, из функций, отмеченных noexcept или throw() in С.)++ Таким образом, даже если вы как-то справляетесь с этим исключением, вы не можете знать, что ваша программа находится в безопасном состоянии. Поэтому лучший способ обработки асинхронного исключения-не обрабатывать его вообще(*). Если один брошен, это означает, что программа содержит ошибку.

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

(*) есть несколько очень редких исключений.


вы можете защитить от переполнения стека, используя хорошие методы программирования, такие как:

  1. будьте очень осторожны с рекурсией, я недавно видел, что это результат плохо написанной рекурсивной функции CreateDirectory, если вы не уверены, что ваш код на 100% в порядке, добавьте переменную защиты, которая остановит выполнение после n рекурсивных вызовов. Или даже лучше не писать рекурсивные функции.
  2. не создавайте огромные массивы в стеке, это могут быть скрытые массивы, такие как очень большой массив как поле класса. Всегда лучше использовать вектор.
  3. будьте очень осторожны с alloca, особенно если он помещен в некоторое определение макроса. Я видел множество так в результате макросов преобразования строк, помещенных в циклы for, которые использовали alloca для быстрого выделения памяти.
  4. убедитесь, что ваш размер стека оптимален, это более важно для встроенных платформ. Если поток не делает много, то дайте ему небольшой стек, иначе используйте больший. Я знаю резервацию. должен принимать только некоторый диапазон адресов-не физическую память.

Это самые серьезные причины, которые я видел за последние несколько лет.

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


C++ - мощный язык, и с этой силой приходит способность стрелять в ногу. Я не знаю какого-либо портативного механизма для обнаружения и исправления/отмены при переполнении стека. Конечно, любое такое определение будет реализации. Например, g++ предоставляет -fstack-protector чтобы помочь контролировать использование стека.

В общем, лучше всего быть активным, избегая больших переменных на основе стека и осторожным с рекурсивными вызовами.


Re: расширяемые стеки. Вы могли бы дать себе больше пространства для стека с чем-то вроде этого:

#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;
}

это синтаксис ассемблера gcc.


#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;
}