рекурсия c с большими числами для поиска мода

#c #recursion #modulo

#c #рекурсия #modulo

Вопрос:

Привет, практикуясь в рекурсии, я нашел упражнение для поиска модулей без использования оператора%. Итак, я написал свою функцию, и все работает нормально. За исключением случаев, когда я набираю числа из 5 цифр или более, эта функция завершается с ошибкой. И я не уверен, делаю ли я что-то неправильно или это сбой, потому что слишком много вызовов. Если вызовов слишком много, это нормально? Действительно ли может быть слишком много вызовов функций? Как я могу предотвратить это, если в будущем у меня будет функция рекурсии, которая действительно полезна? Потому что для меня это не имеет смысла на самом деле. Я выполнил рекурсию ханойской башни, и у нее никогда не возникало этой проблемы, независимо от того, сколько дисков я хочу переместить.

Вот моя функция, а также предпосылка, что оба числа всегда положительны:

 #include <iostream>

using namespace std;
int modulo(int n, int m) {
    if (n < m) return n;
    else return modulo(n - m, m);
}

int main()
{
    int n{ 0 }, m{ 0 };
    char check{ 'a' };
    do {
        cout << "Enter 2 positive integers to calculate their modulo: ";
        cin >> n >> m;
        if (cin.fail()) {
            cerr << "Numbers must be a positive integers. Please try again.n";
            cin.clear(); //clear input stream
            cin.ignore(std::numeric_limits<std::streamsize>::max(), 'n'); //discard any unprocessed characters
        }
        else if (n < 0 || m <= 0) {
            cerr << "Numbers must be positive and second number cannot be zero.nPlease try again.n";
        }
        else {
            cout << "n%m = " << n << "%" << m << " = " << modulo(n, m) << endl;
            cout << " Try again? (enter 'n' to quit): ";
            cin >> check;
        }
    } while (check != 'n');
}
  

Ошибка заключается:

Необработанное исключение в 0x00007FF77D5C2793 в GuessNumber.exe: 0xC00000FD: Переполнение стека (параметры: 0x0000000000000001, 0x0000006F322F3F30).

для чисел, которые я пробовал 40001% 10, это работает, но 44001% 10 терпит неудачу, и все, что после 44001, также терпит неудачу для меня. я не пробовал никаких других чисел

Комментарии:

1. Не могли бы вы, пожалуйста, добавить распечатку ошибки, если таковая имеется?

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

3. Какие наименьшие числа вызывают ошибку?

4. Ошибка действительно является переполнением стека. Я просто не понимаю, почему я получаю его. Необработанное исключение в 0x00007FF77D5C2793 в GuessNumber.exe: 0xC00000FD: Переполнение стека (параметры: 0x0000000000000001, 0x0000006F322F3F30). для чисел я попробовал 40001% 10, это работает, но 44001% 10 не удается

5. Вы пробовали свою программу Tower of Hanoi с 40000 дисками?

Ответ №1:

Если рекурсия слишком глубока, то программе не хватает стековой памяти. Это называется Stack Overflow.

 int modulo(int n, int m) 
{ 
    if (n < m) return n; 
    else return modulo(n - m, m); 
}
  

Например, modulo(1000000, 2) вызывает modulo(999998, 2) , который вызывает modulo(999996, 2) , и так далее, пока modulo(0, 2) в конце в стеке не останется 500001 активных modulo функций. Два целых числа, адрес возврата и указатель фрейма должны занимать не менее 16 байт на вызов функции в любой разумной системе. Всего 8 мегабайт стекового пространства, что обычно превышает максимальный размер стека.

Каждый вызов функции должен ждать результатов следующей функции, пока она не сможет завершить свое вычисление и вернуть результат. До тех пор, пока он не вернет, стек должен был содержать все переменные, параметры и обратный адрес. Адрес возврата — это местоположение в программе, где программа должна возобновиться после return инструкции.

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

В некоторых случаях компилятор может преобразовать рекурсию в цикл. В этом случае, поскольку рекурсия выполняется в return инструкции, она может просто goto перейти к началу функции, вообще не выполняя вызов. Это называется оптимизацией конечных вызовов:

 int modulo(int n, int m) 
{ 
    start:
    if (n < m) return n; 
    else {
       n -= m;
       goto start;
    }
}
  

Если бы вы включили оптимизацию (-O2 в clang или G , или release mode на Visual C ), сбоя бы не произошло, поскольку компилятор создал бы цикл вместо рекурсии. Чтобы избежать сбоя, просто включите оптимизацию.

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

Ответ №2:

Вы выполняете рекурсию на глубину 4400, что создает проблемы. Также здесь в этом нет необходимости, потому что вы можете реализовать тот же алгоритм с циклом:

 while (n >= m) n -= m ;
return n ;