Оказывается, что в некоторых компиляторах с правильными флагами оптимизации это не вызовет переполнения стека! На самом деле я пытался скомпилировать вашу программу в g++
. При оптимизации по умолчанию это вызывает переполнение стека, но на уровне оптимизации -O3
оно просто входит в бесконечный цикл.
Причина различия между бесконечной рекурсией и бесконечными циклами связана с тем, что компилятор по умолчанию реализует эти конструкции. Циклы исторически были реализованы с использованием инструкций ветвления, которые сообщают процессору о необходимости выполнения в другой части программы. Все эти инструкции выполняют переход к другому счетчику программ, который просто изменяет содержимое регистра. С другой стороны, вызовы функций реализуются путем добавления новой записи активации в стек для кодирования аргументов, адреса возврата и т. Д., Чтобы при возврате функции она знала, куда возвращаться.
Тем не менее, это не «способ» вызова функций или ответвлений. Теоретически вы можете реализовать циклы, используя вызовы функций и возвраты, хотя ни один компилятор не делает этого. Точно так же с функциями, которые являются хвостово-рекурсивными (как в вашем примере), компиляторы часто достаточно умны, чтобы исключить все манипуляции со стеком и преобразовать их в наивную инструкцию ветвления, чтобы избежать накладных расходов на установку и демонтаж стека.
Короче говоря, причина, по которой вы получаете другое поведение, основана на том, как компилятор решает реализовать код. Если он достаточно умен, чтобы видеть, что ему не нужно выполнять дорогостоящую настройку вызова функции, тогда он преобразует вызов в простую инструкцию цикла и выполняет цикл навсегда. Если он этого не обнаружит, он обратится к наивному механизму вызова функций.