不是内联函数

Not inlineable functions

本文关键字:函数      更新时间:2023-10-16

我必须展示在 c++ 中无法内联的函数。

为了检查这一点,我设置了标志-Winline

使用递归函数,我能够生成一个无法内联的函数。

但我尝试对继承和"虚拟"关键字进行相同的尝试。但是我无法让编译器抱怨某些内容无法内联。

我知道这个话题已经涵盖了很多。但我只是没有找到一个工作的例子。我的编译器是"聪明"吗:-)

我试过这个:

class virt1
{
public:
    virt1(){};
    inline virtual int virtFunc(int a){ return a*a; };
    virtual ~virt1(){};
};
class virt2 : public virt1
{
public:
    virt2(){};
    inline virtual int virtFunc(int a){ return a+a;};
    virtual ~virt2(){};
};
void testVirtFunc(virt2 &obj)
{
     std::cout << obj.virtFunc(2);
}

没有"内联"或"非内联"函数这样的概念。对函数只有可内联或不可内联的调用。被声明inline的属性是函数本身的属性。可内联(或不可内联)的属性是特定调用的属性。这是两个不同的概念。

你似乎把这两个完全不相关的概念混为一谈。

每个函数都可以声明inline 。虚函数可以声明inline 。递归函数也可以声明为inline。这没什么奇怪的。你认为这在某种程度上是非法的,这是完全没有根据的。声明函数inline始终是合法的。

同时,对这些函数的实际调用是否会内联是一个完全不同的独立问题。对某个函数的一个调用可以是内联的,而对同一函数的另一个调用可以是不可内联的。"内联"再次是每个调用的属性。

确实,动态调度的虚拟调用无法内联。同时,在智能编译器可以在编译时正确预测虚拟调用中使用的对象的动态类型的情况下,编译器可能会生成对虚拟函数的直接调用(不使用动态调度的调用)。此调用可以轻松内联。

例如,给定您的声明,此调用

virt1 *v = rand() % 2 ? new virt1() : new virt2();
v->virtFunc(5);

无法内联。但这些电话

virt2 v2;
v2.virtFunc(6);
virt1* v1 = &v2;
v1->virtFunc(7);

可以毫无问题地内联。

通过指向函数的指针进行的间接调用通常不能内联。但是,如果编译器在编译时以某种方式知道指针的确切值,则可以将调用替换为直接调用并内联。

递归函数调用也可以内联(尽管您不这么认为)。它们可以内联到某个固定的递归深度。这就是大多数编译器内联递归函数的方式:它们使用深度限制的内联来"解包"递归,就像解包循环一样。

内联的意思比编译器的"寄存器"略多。(就像在编译器中一样,在 register 关键字被弃用之前很久就完全忽略了它,从某种意义上讲,命令不是相似的,它们不是,甚至不是)

过去,他们就像"

我会珍惜用户的这些知识",现在他们就像"感谢您对此事的投入"然后丢弃它。他们的处境比我们判断的要好得多。

减少抽象的惩罚方面已经做了很多工作。这意味着编译器可以通过一些比以前更好的复杂语句来推断出事物的类型,从而推迟了对向表跳转。如果它能弄清楚叫什么,它可能会决定它是否值得内联和类似的东西。

模板用于暗示内联,同样,如果您说内联编译器幽默,那么您就像一个孩子在做纸牌技巧(并且弄错了)。

编译器能够确定要调用的方法的情况下,没有理由不能内联虚拟方法。就像它可以优化变量一样,它可以优化函数指针,而虚拟基本上隐藏了虚拟指针的结构。

所以回到过去的日子里,如果我做some_derived_instance.some_vritual_method();没有理由不能内联。内联从来没有说"它必须ININE",就像寄存器一样,只是给了编译器一个提示。

更直接地回答问题

您的代码不够复杂,不会混淆编译器。 -Winline 在编译器最终实现调用时,如果函数无法内联,则会发出警告。

您会发现很难在一个代码文件中使代码变得足够复杂,以至于无法分辨这一点。别名分析意味着工会不会隐藏它。编译器对代码有很好的理解,可以在一定程度上"运行代码"。

要真正测试这一点,请将类的代码放在一个文件中,并在另一个文件中使用基本 clas。然后编译器在编译测试函数时无法知道发生了什么,因为调用站点位于另一个对象文件中。

不要使用链接时间优化,编译仍然可能是内联的(使用 GCC,他们只是流式传输 GIMPLE IR,其中包含所有附加的信息)

底线

试图愚弄现代编译器是个婊子。

回复:递归

编译器不会优化代码,而是优化一些中间表示形式。它可以"内联递归",因为它将递归函数表示为调用站点上的一堆代码。

没有递归限制,它对愿意创建的 blob 的大小有限制。如果你"调整"内联变得超级激进,你可以让GCC产生巨大的输出。想想俄罗斯娃娃,但每个娃娃都有 N 个孩子。