Ответ 1
Если вы скомпилируете с помощью 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
и положите на фактор-параметр, чтобы избавиться от умножения.
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;
}
}
Наконец, inline 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;
}
}
(Естественно, это не самый разумный способ записи функции вручную).