Подтвердить что ты не робот

Как обрабатывать или избегать в С++

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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


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

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

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

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

Ответ 2

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

  • Будьте очень осторожны с рекурсией, я недавно увидел SO, вызванный плохо написанной рекурсивной функцией CreateDirectory, если вы не уверены, что ваш код на 100% нормально, а затем добавьте защитную переменную, которая остановит выполнение после N рекурсивных вызовов. Или даже лучше не записывать рекурсивные функции.
  • Не создавайте огромные массивы в стеке, это могут быть скрытые массивы, такие как очень большой массив, как поле класса. Его всегда лучше использовать вектор.
  • Будьте очень осторожны с alloca, особенно если он помещен в какое-то определение макроса. Я видел множество SO, полученных из макросов преобразования строк, помещенных в циклы for, которые использовали alloca для быстрого выделения памяти.
  • Убедитесь, что размер стека оптимален, что более важно для встроенных платформ. Если вы ничейки не делаете много, то дайте ему небольшой стек, иначе используйте больше. Я знаю, что резервирование должно принимать только диапазон адресов, а не физическую память.

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

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

Ответ 3

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

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

Ответ 4

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.

Ответ 5

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