1

我必须展示在 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);
}
4

2 回答 2

6

没有“可内联”或“不可内联”函数这样的概念。只有可内联或不可内联的函数调用。被声明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);

可以内联没有任何问题。

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

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

于 2013-11-12T23:49:58.593 回答
3

内联意味着比编译器“注册”略多。(因为在编译器中完全忽略了 register 关键字早在它被弃用之前,不是在命令相似的意义上,它们不是,甚至不是)

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

为了减少抽象的代价,我们做了很多工作。这意味着编译器可以通过一些非常复杂的语句推断出比以前更好的东西的类型,从而推迟 v-table 跳转。如果它可以计算出被调用的内容,它可能会决定是否值得内联之类的东西。

用于暗示内联的模板,同样,如果你说内联编译器幽默你喜欢一个孩子做纸牌把戏(并且弄错了)。

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

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

更直接地回答问题

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

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

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

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

底线

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

回复:递归

编译器不优化代码,它优化一些中间表示。它可以“内联递归”,因为它将递归函数表示为调用站点上的一段代码。

没有递归限制,它对愿意创建的 blob 的大小有限制。如果您将内联“调整”为超级激进,则可以使 GCC 生成巨大的输出。想想俄罗斯娃娃,但每个娃娃都有 N 个孩子。

于 2013-11-12T23:38:46.387 回答