Как уже отмечали другие, это возможно только в том случае, если (1) ваш компилятор поддерживает оптимизацию хвостового вызова, и (2) если ваша функция подходит для такой оптимизации. Оптимизация заключается в повторном использовании существующего стека и выполнении JMP (т.е. GOTO в сборке) вместо CALL.
На самом деле, ваша примерная функция действительно подходит для такой оптимизации. Причина в том, что последнее, что ваша функция делает перед возвратом, это сам вызов; он не должен ничего делать после последнего вызова funcnew()
. Однако, только некоторые компиляторы будут выполнять такую оптимизацию. GCC, например, сделает это. Для получения дополнительной информации см. Какие, если таковые имеются, компиляторы C ++ выполняют оптимизацию хвостовой рекурсии?
Классическим материалом для этого является факториальная функция. Давайте создадим рекурсивную факториальную функцию, которая не подходит для оптимизации хвостового вызова (TCO).
int fact(int n)
{
if ( n == 1 ) return 1;
return n*fact(n-1);
}
Последнее, что он делает, это умножает n
на результат из fact(n-1)
. Каким-то образом исключив эту последнюю операцию, мы сможем повторно использовать стек. Давайте введем переменную-аккумулятор, которая вычислит ответ для нас:
int fact_helper(int n, int acc)
{
if ( n == 1 ) return acc;
return fact_helper(n-1, n*acc);
}
int fact_acc(int n)
{
return fact_helper(n, 1);
}
Функция fact_helper
выполняет свою работу, тогда как fact_acc
- это просто вспомогательная функция для инициализации переменной аккумулятора.
Заметьте, как последняя вещь, которую fact_helper
делает, это вызывает себя. Этот CALL может быть преобразован в JMP путем повторного использования существующего стека для переменных.
С помощью GCC вы можете убедиться, что он оптимизирован для перехода, просмотрев сгенерированную сборку, например gcc -c -O3 -Wa,-a,-ad fact.c
:
...
37 L12:
38 0040 0FAFC2 imull %edx, %eax
39 0043 83EA01 subl $1, %edx
40 0046 83FA01 cmpl $1, %edx
41 0049 75F5 jne L12
...
Некоторые языки программирования, такие как Scheme , фактически гарантируют, что соответствующие реализации будут выполнять такую оптимизацию. Они даже сделают это для нерекурсивных хвостовых вызовов.