递归函数可以内联吗?

时间:2008-10-10 05:30:44

标签: c++ c compiler-construction

inline int factorial(int n)
{
    if(!n) return 1;
    else return n*factorial(n-1);
}

当我阅读this时,如果编译器没有正确处理,上面的代码会导致“无限编译”。

编译器如何决定是否内联函数?

9 个答案:

答案 0 :(得分:134)

首先,函数的inline规范只是一个提示。编译器可以(并且经常)完全忽略inline限定符的存在或不存在。话虽如此,编译器可以内联递归函数,就像它可以展开无限循环一样。它只需要限制它将“展开”该功能的级别。

优化编译器可能会转换此代码:

inline int factorial(int n)
{
    if (n <= 1)
    {
        return 1;
    }
    else
    {
        return n * factorial(n - 1);
    }
}

int f(int x)
{
    return factorial(x);
}

进入此代码:

int factorial(int n)
{
    if (n <= 1)
    {
        return 1;
    }
    else
    {
        return n * factorial(n - 1);
    }
}

int f(int x)
{
    if (x <= 1)
    {
        return 1;
    }
    else
    {
        int x2 = x - 1;
        if (x2 <= 1)
        {
            return x * 1;
        }
        else
        {
            int x3 = x2 - 1;
            if (x3 <= 1)
            {
                return x * x2 * 1;
            }
            else
            {
                return x * x2 * x3 * factorial(x3 - 1);
            }
        }
    }
}

在这种情况下,我们基本上将该功能内联了3次。一些编译器执行此优化。我记得MSVC ++有一个设置来调整将在递归函数上执行的内联级别(我相信最多20个)。

答案 1 :(得分:23)

实际上,如果您的编译器不能智能地执行操作,它可能会尝试递归地插入inline d函数的副本,从而创建无限大的代码。然而,大多数现代编译器都会认识到这一点。他们可以:

  1. 根本没有内联功能
  2. 将其内联到某个深度,如果它尚未终止,则使用标准函数调用约定调用函数的单独实例。这可以以高性能方式处理许多常见情况,同时为具有大调用深度的罕见情况留下后备。这也意味着你要保留该函数代码的内联版本和单独版本。
  3. 对于案例2,许多编译器都有#pragma你可以设置为指定应该执行此操作的最大深度。在 gcc 中,您还可以使用--max-inline-insns-recursive从命令行传入此内容(请参阅更多信息here)。

答案 2 :(得分:7)

如果可能,AFAIK GCC将对递归函数执行尾调用消除。但是你的函数不是尾递归的。

答案 3 :(得分:6)

编译器创建一个调用图;当检测到一个循环调用自身时,该函数在某个深度后不再内联(n = 1,10,100,无论编译器调到哪个)。

答案 4 :(得分:3)

一些递归函数可以转换为循环,有效地无限地内联它们。我相信gcc可以做到这一点,但我不知道其他编译器。

答案 5 :(得分:2)

请参阅已经给出的答案,说明为什么这通常不起作用。

作为“脚注”,您可以使用template metaprogramming达到您正在寻找的效果(至少对于您正在使用的因子)。粘贴维基百科:

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

答案 6 :(得分:1)

编译器会生成一个调用图来检测这些事情并阻止它们。所以它会看到函数调用自身而不是内联。

但主要是由内联关键字和编译器开关控制(例如,即使没有关键字,也可以让它自动内联小函数。)重要的是要注意Debug编译永远不应该内联,因为callstack不会保留以镜像您在代码中创建的调用。

答案 7 :(得分:1)

“编译器如何决定是否内联函数?”

这取决于编译器,指定的选项,编译器的版本号,可用的内存量等等。

程序的源代码仍然必须遵守内联函数的规则。无论函数是否被内联,您都必须准备它内联的可能性(某些未知次数)。

维基百科关于递归宏通常是非法的声明看起来很不明智。 C和C ++阻止了递归调用,但是通过包含看起来像递归的宏代码,翻译单元不会变成非法的。在汇编程序中,递归宏通常是合法的。

答案 8 :(得分:0)

有些编译器(即Borland C ++)没有内联包含条件语句的代码(if,case,while等等),所以你的例子中的递归函数不会被内联。

相关问题