inline int factorial(int n)
{
if(!n) return 1;
else return n*factorial(n-1);
}
当我阅读this时,如果编译器没有正确处理,上面的代码会导致“无限编译”。
编译器如何决定是否内联函数?
答案 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函数的副本,从而创建无限大的代码。然而,大多数现代编译器都会认识到这一点。他们可以:
对于案例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等等),所以你的例子中的递归函数不会被内联。