Если вы скомпилируете с gcc -O3 -fdump-tree-all
, вы увидите, что первый дамп, в котором рекурсия была превращена в цикл, это foo.c.035t.tailr1
.Это означает, что та же самая оптимизация, которая обрабатывает другие хвостовые вызовы, также обрабатывает этот слегка расширенный случай.Рекурсию в форме n * foo(...)
или n + foo(...)
не так сложно обработать вручную (см. Ниже), и, поскольку можно точно описать, как компилятор может выполнить эту оптимизацию автоматически.
Оптимизацияmain
намного проще: встраивание может превратить это в 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1 * 1
, и если все операнды умножения являются константами, то умножение может быть выполнено во время компиляции.
Обновление : Вот как вы можете вручную удалить рекурсию из foo
, что можно сделать автоматически.Я не говорю, что это метод, используемый GCC, но это одна из реальных возможностей.
Сначала создайте вспомогательную функцию.Он ведет себя точно так же, как foo(n)
, за исключением того, что его результаты умножаются на дополнительный параметр f
.
int foo(int n)
{
return foo_helper(n, 1);
}
int foo_helper(int n, int f)
{
if (n == 0) return f * 1;
return f * n * foo(n-1);
}
Затем превращайте рекурсивные вызовы foo
в рекурсивные вызовы foo_helper
и полагайтесьна параметр factor, чтобы избавиться от умножения.
int foo(int n)
{
return foo_helper(n, 1);
}
int foo_helper(int n, int f)
{
if (n == 0) return f;
return foo_helper(n-1, f * n);
}
Превратите это в цикл:
int foo(int n)
{
return foo_helper(n, 1);
}
int foo_helper(int n, int f)
{
restart:
if (n == 0) return f;
{
int newn = n-1;
int newf = f * n;
n = newn;
f = newf;
goto restart;
}
}
Наконец, встроенный foo_helper
:
int foo(int n)
{
int f = 1;
restart:
if (n == 0) return f;
{
int newn = n-1;
int newf = f * n;
n = newn;
f = newf;
goto restart;
}
}
(Естественно, это не самый разумный способ написать функцию вручную.)