关于C ++内联函数的两个问题

时间:2010-08-22 17:06:56

标签: c++ inline

我在C ++中编译内联函数时有疑问。

递归函数可以与内联一起使用吗?如果是,那么请描述如何。

我确信循环不能用它但我已经阅读了某个地方递归会工作,如果我们传递常量值。

我的朋友给我发送了一些内联递归函数作为常量参数,并告诉我这将是有效的但是在我的笔记本电脑上不起作用,在编译时没有错误但在运行时没有显示任何内容,我必须通过强制中断来终止它。

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

这是如何工作的?

我正在使用turbo 3.2


另外,如果内联函数代码太大,那么编译器能否在正常函数中自动更改?

感谢

6 个答案:

答案 0 :(得分:4)

这个特殊功能绝对可以内联。这是因为编译器可以发现这种特殊形式的递归(尾递归)可以简单地转换为普通循环。通过正常的循环,它完全没有问题。

编译器不仅可以内联它,它甚至可以计算编译时常量的结果,而不会为函数生成任何代码。

使用GCC 4.4

int fac = f(10); 

制作了这条指令:

movl    $3628800, 4(%esp)

您可以在检查程序集输出时轻松验证该函数是否确实内联于编译时未知的输入。

答案 1 :(得分:3)

我想你的朋友试图说如果给定一个常量,编译器可以在编译时完全计算结果,只是在调用站点内联回答。 c ++ 0x实际上有一种称为constexpr的机制,但是允许代码的复杂程度是有限的。但即使使用当前版本的c ++,也是可能的。它完全取决于编译器。

这个函数可能是一个很好的候选者,因为它显然只引用参数来计算结果。有些编译器甚至有non-portable attributes来帮助编译器做出决定。例如,gcc具有pureconst属性(在我刚刚链接的页面上列出),通知编译器此代码仅对参数进行操作且没有副作用,使其更可能是在编译时计算。

即使没有这个,它仍然会编译!原因是如果编译器决定允许编译器不内联。将内联关键字视为建议而不是指令。

假设编译器在编译时没有计算整个事情,如果没有应用其他优化(参见下面的编辑),内联是不可能的,因为它必须有一个实际的函数来调用。但是,它可能会部分内联。在这种情况下,编译器将内联初始调用,但也会发出函数的常规版本,该函数将在递归期间被调用。

关于你的第二个问题,是的,大小是编制者用来决定是否适合内联的一个因素。

如果在笔记本电脑上运行此代码需要很长时间,那么您可能只是给它非常大的值而且只需要很长时间来计算答案...代码看起来不错,但请保持请注意,高于13!的值将溢出32位int。你试图通过什么价值?

了解实际发生情况的唯一方法是编译生成的程序集。

PS:如果您关注优化,可能需要研究一个更现代的编译器。对于Windows,有MingW和免费版本的Visual C ++。对于* NIX,当然有g ++。

编辑:还有一个名为Tail Recursion Optimization的东西,允许编译器将某些类型的递归算法转换为迭代,使它们成为内联的更好候选者。 (除了使它们更有效地堆栈空间)。

答案 2 :(得分:1)

递归函数可以内联到某些有限的递归深度。有些编译器有一个选项,可以让您指定内联递归函数时想要的深度。基本上,编译器“展平”几个嵌套的递归级别。如果执行到达“扁平化”代码的末尾,则代码以通常的递归方式调用自身,依此类推。当然,如果递归的深度是运行时值,则编译器必须在每次执行“展平”代码内的每个原始递归步骤之前检查相应的条件。换句话说,内联递归函数没有什么不寻常之处。这就像展开一个循环。不要求参数保持不变。

你的意思是“我确信循环无法正常工作”并不清楚。它似乎没有多大意义。带循环的函数可以很容易地内联,并且没有什么奇怪的。

你想说什么“什么也不显示”的例子也不清楚。代码中没有任何内容可以“显示”任何东西。难怪它“什么都不显示”。最重要的是,您发布了无效代码。如果没有显式的返回类型,C ++语言不允许函数声明。

关于你的上一个问题,是的,编译器可以完全自由地将内联函数实现为“普通”函数。它与功能“太大”无关。它与特定编译器使用的或多或少复杂的启发式标准有关,以决定内联函数。它可以考虑到大小。它可以考虑其他因素。

答案 3 :(得分:0)

您可以内联递归函数。编译器通常将它们展开到某个深度 - 在VS中你甚至可以拥有一个编译指示,编译器也可以进行部分内联。它实质上将其转换为循环。此外,正如@Evan Teran所说,编译器不会强制内联你建议的函数。它可能完全无视你,这完全有效。

代码的问题不在于内联函数。这个论点的不变与否是非常无关紧要的,我敢肯定。

另外,说真的,得到一个新的编译器。适用于笔记本电脑运行的操作系统的现代免费编译器。

答案 4 :(得分:0)

要记住的一件事 - 根据标准,内联是一个建议,而不是绝对的保证。在递归函数的情况下,编译器并不总是能够计算递归限制 - 现代编译器变得非常聪明,先前的响应显示编译器评估常量内联并简单地生成结果,但考虑

bigint fac = factorialOf(userInput)

编译器无法想出那个........

作为旁注,大多数编译器倾向于忽略调试版本中的内联,除非明确指示不这样做 - 使调试更容易

只要编译器能够令人满意地重新排列内部表示以在结束时获得递归条件测试,尾递归就可以转换为循环。在这种情况下,它可以执行代码生成,将递归函数重新表示为一个简单的循环

就尾递归重写,递归函数的部分扩展等问题而言,这些通常由优化开关控制 - 所有现代编译器都能够进行非常显着的优化,但有时候确实会出错。

答案 5 :(得分:0)

请记住,内联关键字只是向编译器发送请求,而不是命令。如果函数定义太长或太复杂,compliler可能会忽略yhis请求,并将函数编译为普通函数。

在内联函数可能无效的某些情况下

  1. 对于返回值的函数,如果存在循环,开关或goto。
  2. 对于不返回值的函数,如果存在return语句。
  3. 如果函数包含静态变量。
  4. 如果行内函数是递归的。
  5. 因此在C ++中,内联递归函数可能无法正常工作。