recursion函数可以内联吗?
inline int factorial(int n) { if(!n) return 1; else return n*factorial(n-1); }
正如我正在读这个 ,发现上面的代码将导致“无限编译”,如果不正确的编译器处理。
编译器如何决定是否内联一个函数?
首先,函数的inline
规范只是一个提示。 编译器可以(而且经常)完全忽略inline
限定符的存在与否。 这样说,编译器可以内联一个recursion函数,就像展开一个无限循环一样。 它只是对它将“展开”function的级别进行限制。
一个优化编译器可能会变成这样的代
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 ++有一个设置来调整将在recursion函数(最多20,我相信)上执行内联的级别。
事实上,如果你的编译器不能智能地执行,它可能会尝试recursion地插入你的inline
d函数的副本,从而创build无限大的代码。 然而,大多数现代编译器会认识到这一点。 他们可以:
- 不内联函数
- 将它内联到一定的深度,如果尚未结束,则使用标准函数调用约定调用函数的单独实例。 这可以以高性能的方式处理许多常见的情况,同时留下较大通话深度的罕见情况。 这也意味着你保持这个函数代码的内联和分离版本。
对于情况2,许多编译器都可以设置#pragma
来指定应该完成的最大深度。 在gcc中 ,你也可以通过命令行传入--max-inline-insns-recursive
(参见这里的更多信息)。
如果可能的话,AFAIK GCC会对recursion函数进行tail call消除。 你的函数不是尾recursion的。
编译器创build一个调用图; 当检测到循环调用自身时,该函数不再在特定深度(n = 1,10,100,无论编译器调整到什么程度)之后内联。
一些recursion函数可以转化为循环,这些循环有效地将它们无限化。 我相信海湾合作委员会可以做到这一点,但我不知道其他编译器。
编译器会调用一个调用图来检测这些事情并阻止它们。 所以它会看到该函数自己调用,而不是内联。
但主要是由内联关键字和编译器开关控制(例如,即使没有关键字,也可以自动内联小函数)。重要的是要注意,debugging编译不应该内联,因为调用堆栈不会被保留为镜像您在代码中创build的调用。
“编译器如何决定是否内联一个函数?
这取决于编译器,指定的选项,编译器的版本号,可能有多less内存等等。
程序的源代码仍然必须遵守内联函数的规则。 无论函数是否被内联,你都要准备将它内联的可能性(一些未知的次数)。
recursionmacros的维基百科声明通常是非法的,看起来相当缺乏信息。 C和C ++阻止recursion调用,但是通过包含看起来像是recursion的macros代码,翻译单元不会变成非法的。 在汇编程序中,recursionmacros通常是合法的。
看到为什么这通常不会工作已经给出的答案。
作为一个“脚注”,您可以使用模板元编程达到您正在寻找的效果(至less对于您使用的阶乘作为示例)。 从维基百科粘贴:
template <int N> struct Factorial { enum { value = N * Factorial<N - 1>::value }; }; template <> struct Factorial<0> { enum { value = 1 }; };
一些编译器(即Borland C ++)不内联包含条件语句的代码(如果,例如,等等),这样你的例子中的recursion函数就不会被内联。