我可以期望此调用内联吗?

Can I expect this call to be inlined?

本文关键字:调用 期望 我可以      更新时间:2023-10-16
A * a = new B();
a->foo();

假设 B 派生 A,foo(( 是一个虚函数。这个例子非常频繁,问题是在某些地方据说编译器不会尝试内联它,而在其他地方则恰恰相反。

我个人看不出为什么这个调用不能内联,因为在编译时很容易分辨出哪个函数被调用。

编辑 1:我知道"一般情况",也知道编译器需要许多因素来决定是否内联。如果我问编译器是否可以内联这个特定的调用,这个问题的格式可能会更好。

我问这个问题的原因是这个C++常见问题解答中的一句话,它说:

当通过指针或引用引用对象时,无法内联对虚函数的调用,因为调用必须动态解析。

如果编译器可以以某种方式推断出a指向的对象是B的实例(即,如果代码是微不足道的,就像在你的例子中一样(,那么它可以去虚拟化调用 - 尽管它不是必需的。

但关键是,一般来说,调用是在运行时解析的,因为你不知道(编译器也不知道!(a指向的对象的动态类型是什么。

换句话说,如果编译器不知道*a的动态类型是什么,因此直到运行时才知道应该调用哪个foo()函数,则不可能在编译时取消虚拟化调用并内联它。

澄清一下,g++ 显然至少正确地内联了一些微不足道的虚拟调用情况; 这类似于您的情况,只是稍微调整了一下以获得更具可读性的汇编程序:)

void test(int);
class A { public: virtual int Bop() { return 1; } };
class B : public A { virtual int Bop() { return 4711; } };
int main() {
  A* a = new B();
  test(a->Bop());
}
$ g++ -O99 -S -c test.cpp
$ less test.s
...
main:
.LFB2:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $8, %edi
    call    _Znwm
    movl    $4711, %edi            // hard coded 4711
    movq    $_ZTV1B+16, (%rax)
    call    _Z4testi               // calls test()
...

想象一下这样的翻译单元:

计算.cpp:

#include "A.h"
int compute(A * p)
{
    return p->get_some_int() * 10;
}

您认为虚拟函数调用如何可能去虚拟化?下面是一个使用示例:

主.cpp:

#include "B.h"
#include "C.h"
int compute(A *);
int main()
{
    A * p = rand() % 2 == 0 ? new B : new C;
    return compute(x);
}

你不能指望编译器内联它。这取决于编译器和所选的优化级别。假设 B::foo 的实现是可见的,并且在赋值和调用之间没有执行任何操作,那么,是的,目视检查表明编译器有足够的信息可用于进行优化。

但并非必须如此。

在许多其他情况下,它没有足够的信息。