Обнаружение переполнения стека во время выполнения заранее
У меня довольно большая рекурсивная функция (также я пишу на C), и хотя я не сомневаюсь, что сценарий переполнения стека крайне маловероятен, он все же возможен. Что меня интересует, так это то, можете ли вы определить, будет ли переполнен стек за несколько итераций, чтобы вы могли выполнить аварийную остановку, не вызывая сбой программы.
4 ответа
Вот простое решение, которое работает для win-32. На самом деле напоминает то, что Wossname уже опубликовал, но менее неприлично:)
unsigned int get_stack_address( void )
{
unsigned int r = 0;
__asm mov dword ptr [r], esp;
return r;
}
void rec( int x, const unsigned int begin_address )
{
// here just put 100 000 bytes of memory
if ( begin_address - get_stack_address() > 100000 )
{
//std::cout << "Recursion level " << x << " stack too high" << std::endl;
return;
}
rec( x + 1, begin_address );
}
int main( void )
{
int x = 0;
rec(x,get_stack_address());
}
На самом языке программирования C это невозможно. В общем, вы не можете легко узнать, что у вас закончился стек до того, как он закончился. Вместо этого я рекомендую установить настраиваемое жесткое ограничение на глубину рекурсии в вашей реализации, чтобы вы могли просто прервать ее, если глубина превышена. Вы также можете переписать свой алгоритм, чтобы использовать вспомогательную структуру данных вместо использования стека через рекурсию, это дает вам большую гибкость для обнаружения состояния нехватки памяти; malloc()
говорит вам, когда это не удается.
Тем не менее, вы можете получить нечто похожее с такой процедурой в UNIX-подобных системах:
- использование
setrlimit
установить предел мягкого стека ниже, чем предел жесткого стека - Установите обработчики сигналов для обоих
SIGSEGV
а такжеSIGBUS
получать уведомления о переполнении стека. Некоторые операционные системы производятSIGSEGV
для этих, другихSIGBUS
, - Если вы получили такой сигнал и решили, что он исходит из переполнения стека, увеличьте предел мягкого стека с помощью
setrlimit
и установите глобальную переменную, чтобы определить, что это произошло. Сделать переменнуюvolatile
так что оптимизатор не мешает вашим равнинам. - В своем коде на каждом шаге рекурсии проверяйте, установлена ли эта переменная. Если это так, отменить.
Это может работать не везде, и требуется специальный код платформы, чтобы узнать, что сигнал поступил из-за переполнения стека. Не все системы (в частности, ранние системы 68000) могут продолжать нормальную обработку после получения SIGSEGV
или же SIGBUS
,
Аналогичный подход был использован оболочкой Bourne для выделения памяти.
Вот наивный метод, но это немного неприлично...
Когда вы вводите функцию в первый раз, вы можете сохранить адрес одной из ваших переменных, объявленных в этой функции. Сохраните это значение вне вашей функции (например, в глобальном). При последующих вызовах сравнивайте текущий адрес этой переменной с кэшированной копией. Чем глубже вы повторяете, тем дальше друг от друга будут эти два значения.
Скорее всего, это вызовет предупреждения компилятора (хранение адресов временных переменных), но дает преимущество в том, что дает вам довольно точный способ точно узнать, какой объем стека вы используете.
Не могу сказать, что я действительно рекомендую это, но это будет работать.
#include <stdio.h>
char* start = NULL;
void recurse()
{
char marker = '@';
if(start == NULL)
start = ▮
printf("depth: %d\n", abs(&marker - start));
if(abs(&marker - start) < 1000)
recurse();
else
start = NULL;
}
int main()
{
recurse();
return 0;
}
Альтернативный метод состоит в том, чтобы узнать предел стека в начале программы и каждый раз в вашей рекурсивной функции проверять, был ли достигнут этот предел (в пределах некоторого запаса прочности, скажем, 64 КБ). Если это так, прервать; если нет, продолжайте.
Предел стека в системах POSIX можно узнать с помощью getrlimit
системный вызов.
Пример кода, который является потокобезопасным: (примечание: он предполагает, что стек увеличивается в обратном направлении, как на x86
!)
#include <stdio.h>
#include <sys/time.h>
#include <sys/resource.h>
void *stack_limit;
#define SAFETY_MARGIN (64 * 1024) // 64 kb
void recurse(int level)
{
void *stack_top = &stack_top;
if (stack_top <= stack_limit) {
printf("stack limit reached at recursion level %d\n", level);
return;
}
recurse(level + 1);
}
int get_max_stack_size(void)
{
struct rlimit rl;
int ret = getrlimit(RLIMIT_STACK, &rl);
if (ret != 0) {
return 1024 * 1024 * 8; // 8 MB is the default on many platforms
}
printf("max stack size: %d\n", (int)rl.rlim_cur);
return rl.rlim_cur;
}
int main (int argc, char *argv[])
{
int x;
stack_limit = (char *)&x - get_max_stack_size() + SAFETY_MARGIN;
recurse(0);
return 0;
}
Выход:
max stack size: 8388608
stack limit reached at recursion level 174549