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

Я не понимаю, почему сначала эта рекурсия работает (печатает значения от 2 до 4764), а потом неожиданно выдает исключение.

Я также не понимаю, почему я могу сказать return in void function и фактически вернуть что-то отличное от return;

Кто-нибудь может объяснить это две проблемы?

#include<iostream>
using namespace std;

void function(int& a){
    a++;
    cout << a << endl;
    return function(a);
}

void main() {

    int b = 2;
    function(b);

    system("pause>0");
}
c++
0
private7 31 Май 2019 в 01:18

2 ответа

Лучший ответ

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

Хотя рекурсивные программы полезны для определенных структур данных, они не должны проходить несколько тысяч уровней и обычно добавлять условие остановки (в данном случае даже проверяя, a > some_limit) ли это, чтобы определить точку, в которой они углубились и нуждаются перестать добавлять больше вещей в стек (обычный return;).

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

0
MBer 30 Май 2019 в 22:57

На платформах x86-64, таких как ваш ноутбук или настольный компьютер, функции вызываются одним из двух способов:

  • с call инструкцией по сборке
  • с jmp инструкцией по сборке

В чем разница? Инструкция по сборке call содержит дополнительные инструкции: после вызова функции код вернется в то место, откуда он был вызван. Чтобы отслеживать, где она находится, функция использует память в стеке. Если рекурсивная функция вызывает себя, используя call, то при повторном обращении она будет использовать все больше и больше стека, что в конечном итоге приведет к переполнению стека.

С другой стороны, инструкция jmp просто указывает процессору перейти к той части кода, где хранится другая функция. Если функция вызывает себя сама, то процессор просто jmp вернется к началу функции и запустит ее заново с обновленными параметрами. Это называется оптимизацией хвостового вызова и во многих распространенных случаях полностью предотвращает переполнение стека, поскольку стек не растет.

Если вы скомпилируете свой код на более высоком уровне оптимизации (скажем, -O2 в GCC), тогда компилятор будет использовать оптимизацию хвостового вызова, и у вашего кода не будет переполнения стека.

0
J. Antonio Perez 30 Май 2019 в 23:40
56386330