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

Обнаружение во время выполнения

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

4b9b3361

Ответ 1

Вот простое решение, которое работает для 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());
}

Ответ 2

В самом языке программирования C это невозможно. В общем, вы не можете легко знать, что у вас закончились стеки до истечения срока. Я рекомендую вам вместо этого установить настраиваемый жесткий предел на глубину рекурсии в вашей реализации, поэтому вы можете просто прервать, когда глубина будет превышена. Вы также можете переписать свой алгоритм для использования вспомогательной структуры данных вместо использования стека через рекурсию, что дает вам большую гибкость для обнаружения состояния нехватки памяти; malloc() сообщает, когда он не работает.

Однако вы можете получить что-то подобное с такой же процедурой в UNIX-подобных системах:

  • Используйте setrlimit для установки предела мягкого стека ниже предела жесткого стека
  • Установите обработчики сигналов для SIGSEGV и SIGBUS, чтобы получить уведомление о переполнении стека. Некоторые операционные системы производят SIGSEGV для них, другие SIGBUS.
  • Если вы получите такой сигнал и определите, что он исходит из, поднимите ограничение на мягкий стек с помощью setrlimit и установите глобальную переменную, чтобы определить, что это произошло. Создайте переменную volatile, чтобы оптимизатор не скрутил ваши равнины.
  • В вашем коде на каждом этапе рекурсии проверьте, установлена ​​ли эта переменная. Если это так, отмените.

Это может не работать повсюду и требовать специфический для платформы код, чтобы узнать, что сигнал поступает из. Не все системы (особенно ранние системы 68000) могут продолжать нормальную обработку после получения SIGSEGV или SIGBUS.

Аналогичный подход использовался оболочкой Bourne для выделения памяти.

Ответ 3

Вот наивный метод, но это немного нехорошо...

Когда вы впервые вводите эту функцию, вы можете сохранить адрес одной из ваших переменных, объявленных в этой функции. Храните это значение вне вашей функции (например, в глобальной сети). В последующих вызовах сравнивается текущий адрес этой переменной с кешированной копией. Чем глубже вы рекурсивете, тем дальше будут эти два значения.

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

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

#include <stdio.h>

char* start = NULL;

void recurse()
{
  char marker = '@';

  if(start == NULL)
    start = &marker;

  printf("depth: %d\n", abs(&marker - start));

  if(abs(&marker - start) < 1000)
    recurse();
  else
    start = NULL;
}

int main()
{
  recurse();

  return 0;
}

Ответ 4

Альтернативный метод состоит в том, чтобы узнать предел стека в начале программы и каждый раз в вашей рекурсивной функции проверить, был ли этот предел приближен (в пределах некоторого запаса прочности, скажем, 64 кб). Если это так, отмените; если нет, продолжайте.

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

Пример кода, который является потокобезопасным: ( note: он предполагает, что стек растет в обратном направлении, как на 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