2016-02-05 4 views
12

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

+0

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

+0

Вы также можете просто ограничить глубину рекурсии до максимальной глубины. –

+2

Не совсем дубликат, но связанный с этим: http://stackoverflow.com/questions/199747/how-to-detect-possible-potential-stack-overflow-problems-in-acc-program?rq=1 –

ответ

4

Heres простое решение который работает на победу-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()); 
} 
+0

'std :: cout'? Это не C-код. – kfx

+0

@kfx предоставлен :) – mainactual

+10

Ahh, inline asm для этого теплого, нечеткого, чувство безопасности работы: D Nice. – Wossname

9

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

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

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

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

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

+0

Да, аналогичные хаки возможны в Windows, хотя это намного проще, если вы используете язык, который поддерживает исключения, и может поймать исключение переполнения стека Windows. –

+0

@MartinJames Решение не переносится в любом случае, поэтому в Windows просто используйте все, что Windows API решает проблему. – fuz

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; 
} 
+3

Not thread-safe :( –

+0

@MartinJames Может использовать локальную переменную потока или просто передать указатель в качестве дополнительного аргумента рабочей функции. Но я не вижу, как это лучше, чем просто подсчет глубины рекурсии. – fuz

+0

@MartinJames, очень верно, но это не было обязательным условием :) – Wossname

2

Альтернативный способ узнать предел стека в начале программы, и каждый раз, когда в вашей рекурсивной функции, чтобы проверить, был ли подошел этот предел (в пределах какой-то запас прочности, скажем, 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 
+0

10 kB недостаточно, учитывая, сколько дерьма вы найдете в стеке перед 'main()'. Повторите попытку с более длинным списком аргументов, чтобы увидеть, что 10 кБ недостаточно. Мне интересно, однако, наверняка можно будет как-то найти начало стека. – fuz

+0

10 kB - эвристика, которая работает для меня. Изменил его до 64 кб в ответ. Один из способов узнать адреса области памяти стека - прочитать файл '/ proc//maps', но это очень специфично для платформы. – kfx

Смежные вопросы