【问题标题】:When can the compiler statically bind a call to a virtual function?编译器何时可以将调用静态绑定到虚函数?
【发布时间】:2011-09-03 06:41:32
【问题描述】:

如果类的类型在编译时已知(例如,如果类实例没有通过引用或指针使用,如中所示),我希望编译器能够静态解析对虚函数的函数调用案例 1) 下面)。

但是,我观察到 Visual Studio 2010 的 C++ 编译器有一个奇怪的行为,我想知道编译器是否有任何理由在类的实例时不将调用静态绑定到“正确的”虚函数带有虚函数的是结构中的成员,通过引用访问。

我是否应该期望编译器在下面的案例 2) 中静态绑定对 f() 的调用?即使aA 而不是A&,cr 的“参考”是否会以某种方式传播到 cr.a?

struct A
{
    virtual void f() ;
    virtual ~A() ;
};

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

struct C {
    A a ;
    B b ;
};

C & GetACRef() ;

void test()
{
    // Case 1) The following calls to f() are statically bound i.e.
    // f() is called without looking up the virtual function ptr.
    C c ;  
    c.a.f() ;
    c.b.f() ;
    A a ;
    a.f() ;

    // Case 2) The following calls to f() go through the dynamic dispatching
    // virtual function lookup code. You can check if you generate the .asm
    // for this file.
    C & cr = GetACRef() ; // Note that C is not polymorphic
    cr.a.f() ; // visual C++ 2010 generates call to f using virtual dispatching
    cr.b.f() ; // visual C++ 2010 generates call to f using virtual dispatching  
}

【问题讨论】:

  • 有趣的是,Clang 似乎也没有优化虚拟调用。有趣。 (如果我将B::f 标记为final,它将成功优化cr.b.f()

标签: c++ performance visual-studio-2010 compiler-construction polymorphism


【解决方案1】:

显然编译器编写者并没有费心解决这个问题。也许它在实际代码中并不常见,不值得他们关注。

如果GetACRef 在别处定义,C 也有可能在那里是多态的,这可能会影响优化。

请注意,编译器并不能解决所有可能的情况,即小型测试程序对人类来说是“显而易见的”。编译器的重点是大型实际程序中经常发生的情况。

【讨论】:

  • GetACref 的定义有什么例子可以使这个优化无效?
  • GetACRef 可以返回对DE 的引用,该引用派生自C(在另一个模块中)。如果编译器不能证明这从不影响代码,它就不能进行优化。
  • 即使这样,cr.a 也必须引用 C::a,对吧?还是我错过了什么?
  • 在这种特定情况下,是的。一般情况下,谁知道呢?
  • @Carlos - 编译器作者试图解决一般情况,而不是特定的测试用例。也许“函数返回对具有未以多态方式使用的虚函数的成员的对象的引用”尚未到达其 TODO 列表的顶部?
【解决方案2】:

我不知道为什么 MSVC 不将您的“案例 2”场景编译为直接调用 - 这当然是可能的。我认为只有微软才能回答。

请注意,GCC 确实执行了您正在寻找的优化(使用 MinGW 4.5.1 和 -O2 测试)。

此外,MSVC 甚至在以下序列中也使用了 vtable 调度(为了清楚起见 - 我正在使用 /Ox 优化选项):

A a;
A& ar(a);
ar.f();

因此,无需函数或容器结构为编译器添加潜在的混淆层 - 编译器没有理由不能在该序列中将 ar.f()a.f() 完全相同。但正如 Bo Persson 所建议的那样,也许这不是一个非常常见的优化方案(或者 MS 根本没有解决这个问题)。同样,只有 MS 的编译器开发人员才能回答。

我不确定我是否会将这种行为归类为“奇怪” - 这是一个错过的优化机会。我不确定这种事情有多普遍。在这种情况下,您是否应该期望编译器生成静态绑定调用?也许。但我认为它没有发生也不足为奇。

也许应该在 MS Connect 上打开一个问题。

【讨论】:

  • 您的示例实际上更复杂。在cr.a.f()中,cr.a的类型必然是A,所以编译器肯定知道动态类型。要了解A& 的含义,需要进行控制流分析,这种分析成本更高,实施起来也更棘手。
  • 很有趣,我使用 Clang 2.9 进行了测试,在您的情况下,它成功地分析了 A& 绑定到 A 并取消虚拟化调用,尽管在 OP 情况下它仍然失败。我会尝试使用更新的版本,这似乎是一个很容易优化的案例(在我的高水平:p)
  • @Matthieu:你说得对,这需要一种不同的分析。我没有考虑到这一点。请注意,GCC 将 ar.f() 解析为静态调用。这两种情况都是我认为编译器通常执行的优化;但是,我不完全确定它在现实世界中的重要性。
  • 这似乎并不常见 :) 不过我不会太担心,编译器已经竭尽全力优化静态调用的虚拟调用,开销最多不应超过 15%...所以它可能是微不足道的,除非函数是微不足道的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
  • 2018-11-21
  • 2019-11-20
  • 1970-01-01
  • 2016-01-21
  • 1970-01-01
相关资源
最近更新 更多