【发布时间】:2010-09-16 10:56:31
【问题描述】:
inline int factorial(int n)
{
if(!n) return 1;
else return n*factorial(n-1);
}
在阅读this时,发现上面的代码如果没有被编译器正确处理会导致“无限编译”。
编译器如何决定是否内联函数?
【问题讨论】:
标签: c++ c compiler-construction
inline int factorial(int n)
{
if(!n) return 1;
else return n*factorial(n-1);
}
在阅读this时,发现上面的代码如果没有被编译器正确处理会导致“无限编译”。
编译器如何决定是否内联函数?
【问题讨论】:
标签: c++ c compiler-construction
首先,函数的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 个)。
【讨论】:
确实,如果您的编译器不能智能地运行,它可能会尝试递归地插入您的 inlined 函数的副本,从而创建无限大的代码。然而,大多数现代编译器都会认识到这一点。他们可以:
对于情况 2,许多编译器都有#pragmas,您可以设置它来指定应该执行此操作的最大深度。在 gcc 中,您还可以从命令行使用 --max-inline-insns-recursive 将其传入(查看更多信息 here)。
【讨论】:
如果可能的话,AFAIK GCC 将对递归函数进行尾调用消除。但是,您的函数不是尾递归的。
【讨论】:
编译器创建调用图;当检测到循环调用自身时,函数在一定深度后不再内联(n=1、10、100,无论编译器调整到什么)。
【讨论】:
请参阅已经给出的答案,了解为什么这通常不起作用。
作为“脚注”,您可以使用template metaprogramming 实现您正在寻找的效果(至少对于您用作示例的阶乘)。从维基百科粘贴:
template <int N>
struct Factorial
{
enum { value = N * Factorial<N - 1>::value };
};
template <>
struct Factorial<0>
{
enum { value = 1 };
};
【讨论】:
一些递归函数可以转换为循环,从而有效地无限内联它们。我相信gcc可以做到这一点,但我不知道其他编译器。
【讨论】:
编译器将创建一个调用图来检测这些事情并阻止它们。所以它会看到函数调用自己而不是内联。
但主要是由 inline 关键字和编译器开关控制(例如,即使没有关键字,你也可以让它自动内联小函数。)重要的是要注意调试编译永远不应该内联,因为调用堆栈不会保留以镜像您在代码中创建的调用。
【讨论】:
“编译器如何决定是否内联函数?”
这取决于编译器、指定的选项、编译器的版本号、可能有多少可用内存等。
程序的源代码仍然必须遵守内联函数的规则。无论函数是否被内联,您都必须为它可能被内联(未知次数)做好准备。
关于递归宏通常是非法的 Wikipedia 声明看起来相当缺乏信息。 C 和 C++ 防止递归调用,但翻译单元不会因为包含看起来像递归的宏代码而变得非法。在汇编器中,递归宏通常是合法的。
【讨论】:
某些编译器(即 Borland C++)不会内联包含条件语句(if、case、while 等)的代码,因此您的示例中的递归函数不会被内联。
【讨论】: