Существует ли какой-либо жесткий проводник на глубине рекурсии в C

Рассматриваемая программа пытается вычислить sum-of-first-n-natural-numbers с помощью recursion. Я знаю, что это можно сделать с помощью простой формулы n*(n+1)/2, но идея здесь заключается в использовании recursion.

Программа выглядит следующим образом:

#include <stdio.h>

unsigned long int add(unsigned long int n)
{
    return (n == 0) ? 0 : n + add(n-1); 
}

int main()
{
    printf("result : %lu \n", add(1000000));
    return 0;
}

Программа хорошо работала для n = 100,000, но когда значение n было увеличено до 1,000,000, это привело к Segmentation fault (core dumped)

Из сообщения gdb было взято следующее.

Program received signal SIGSEGV, Segmentation fault.
0x00000000004004cc in add (n=Cannot access memory at address 0x7fffff7feff8
) at k.c:4

Мой вопрос (ы):

  • Есть ли какой-либо жесткий проводник на recursion depth в C? или зависит от recursion depth от доступной памяти стека?

  • Каковы возможные причины, по которым программа получит сигнал reSIGSEGV?

Ответ 1

Обычно предел будет размером стека. Каждый раз, когда вы вызываете функцию, определенное количество стека употребляется (обычно зависит от функции). Потребляемая сумма - это стек стека, и она восстанавливается, когда функция возвращается. Размер стека почти почти фиксирован, когда программа запускается, либо из-за того, что она указана операционной системой (и часто настраивается там), либо даже жестко запрограммирована в программе.

  • Некоторые реализации могут иметь технику, в которой они могут выделять новые сегменты стека во время выполнения. Но в целом они этого не делают.

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

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

Не так просто понять, сколько пространства стека требуется для каждого вызова функции, и оно будет подлежать уровню оптимизации компилятора. Дешевый способ сделать это в вашем случае - печатать &n каждый раз при его вызове; n, скорее всего, будет в стеке (тем более, что прогаме нужно принять свой адрес, иначе он может быть в регистре), а расстояние между последующими местоположениями будет указывать размер кадра стека.

Ответ 2

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

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

Ответ 3

Стандарт C не определяет минимальную поддерживаемую глубину для вызовов функций. Если бы это было так, что в любом случае было бы очень сложно гарантировать, это было бы упомянуто где-то в разделе 5.2.4 Environmental limits.

Ответ 4

1) Ожидается, что потребление стека будет уменьшено и записано как оптимизация хвостовой рекурсии.

gcc -O3 prog.c

#include <stdio.h>

unsigned long long int add(unsigned long int n, unsigned long long int sum){
    return (n == 0) ? sum : add(n-1, n+sum); //tail recursion form
}

int main(){
    printf("result : %llu \n", add(1000000, 0));//OK
    return 0;
}