【问题标题】:Can a compiler inline a virtual function if I use a pointer in a clear situation?如果我在明确的情况下使用指针,编译器可以内联虚函数吗?
【发布时间】:2011-10-13 18:20:53
【问题描述】:

我已经阅读了Are inline virtual functions really a non-sense?。但是我还是有些疑惑,没有找到答案。

他们说如果情况不模棱两可,编译器应该内联虚函数

但是:

只有当编译器有一个实际对象而不是指针或对象引用时才会发生这种情况。

如果我有一个从A 派生的B 类(其中包含virtual void doSth() 函数)并且我使用B* 指针,而不是A*

B* b = new B;

b->doSth();
  1. 假设B 没有任何子类。 (在编译时)应该调用什么函数是相当明显的。所以可以内联。是真的吗?
  2. 假设B 有一些子类,但这些类没有自己的doSth() 函数。所以编译器应该“知道”唯一要调用的函数是B::doSth()。我猜它没有内联?

【问题讨论】:

  • 我敢打赌,这取决于编译器,您只需查看生成的程序集即可找到答案。你用的是什么编译器?
  • inline virtual function的可能重复
  • 其实这是一个关于编译器的问题。大卫,海湾合作委员会。
  • 它的版本是4.4.1。您认为在 1. 和 2. 情况下内联足够聪明吗?
  • 真正的问题是,即使编译器也能检测到指针的静态类型,为什么还要在堆上分配B?为什么程序员也不能这样做?

标签: c++ compiler-construction virtual inline


【解决方案1】:

B 是否有派生类并不重要。在这种情况下,b 指向 B 对象,因此编译器可以内联调用。

当然,任何体面的现代编译器都可以并且会在你的情况下做到这一点。如果您不使用指针,它会变得容易得多。那么这并不是真正的“优化”。只需查看.-operator 左侧的 AST 节点,您就可以忽略虚拟调用这一事实。但是如果使用指针,则需要跟踪指针的动态类型。但现代编译器能够做到这一点。

编辑:一些实验是有序的。

// main1.cpp
struct A {
  virtual void f();
};

struct B : A { 
  virtual void f();
};

void g() {
  A *a = new A;
  a->f();

  a = new B;
  a->f();
}

// clang -O2 -S -emit-llvm -o - main1.cpp | c++filt
// ...
define void @g()() {
  %1 = tail call noalias i8* @operator new(unsigned int)(i32 4)
  %2 = bitcast i8* %1 to %struct.A*
  %3 = bitcast i8* %1 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @vtable for A, i32 0, i32 2) to i32 (...)**), i32 (...)*** %3, align 4
  tail call void @A::f()(%struct.A* %2)
  %4 = tail call noalias i8* @operator new(unsigned int)(i32 4)
  %5 = bitcast i8* %4 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ([3 x i8*]* @vtable for B, i32 0, i32 2) to i32 (...)**), i32 (...)*** %5, align 4
  %tmp = bitcast i8* %4 to %struct.B*
  tail call void @B::f()(%struct.B* %tmp)
  ret void
}
// ...

可以看出,当a 指向A 和指向B 时,clang 会直接调用f。 GCC 也这样做。

【讨论】:

  • 如果 B 有一个在当前源文件中不可见的子类怎么办?您是说编译器将跟踪分配并“知道”b 包含实际的 B* 而不是孩子?
  • 马克:他就是这么说的。
  • 谢谢。所以看来 GCC 很聪明。
  • @Johannes,感谢您的麻烦。看来我必须停止低估现代编译器的能力;这不是我第一次感到惊讶。
  • @Mark:使用data-flow analysis 推断指向值的静态类型相当简单,这是一种用于许多优化的技术。
【解决方案2】:

当调用没有取消引用 vtable 时,可以内联虚拟成员函数。这可以通过显式调用成员函数来实现。

class A
{
protected:

    int     a;
public:
    inline virtual void Func()
    {
        a = 0;
    }
};

class B : public A
{
public:
    inline virtual void Func()
    {
        a = 1;
    }
};

B   *obj = new B();

obj->Func();    //  Calls B::Func() through vtable;
obj->A::Func(); //  Inlines calls to A::Func();
obj->B::Func(); //  Inlines calls to B::Func();

【讨论】:

  • 是的。但我也想知道如果编译器“看到”obj->Func();,编译器是否足够聪明,不会查看 vtable。
猜你喜欢
  • 2011-06-19
  • 2013-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-28
  • 2011-04-18
相关资源
最近更新 更多