LTO,虚拟化和虚拟表

cco*_*com 15 c c++ compiler-optimization

比较C++中的虚函数和C中的虚拟表,一般编译器(以及足够大的项目)在虚拟化方面做得很好吗?

天真地看来,C++中的虚函数似乎有更多的语义,因此可能更容易虚拟化.

更新: Mooing Duck提到了内联的虚拟化功能.快速检查显示虚拟表的错过优化:

struct vtab {
    int (*f)();
};

struct obj {
    struct vtab *vtab;
    int data;
};

int f()
{
    return 5;
}

int main()
{
    struct vtab vtab = {f};
    struct obj obj = {&vtab, 10};

    printf("%d\n", obj.vtab->f());
}
Run Code Online (Sandbox Code Playgroud)

我的GCC不会内联f,虽然它是直接调用的,即,虚拟化.C++中的等价物,

class A
{
public:
    virtual int f() = 0;
};

class B
{
public:
    int f() {return 5;}
};

int main()
{
    B b;
    printf("%d\n", b.f());
}
Run Code Online (Sandbox Code Playgroud)

甚至内联f.所以C和C++之间存在第一个区别,尽管我不认为C++版本中添加的语义在这种情况下是相关的.

更新2:为了在C中进行虚拟化,编译器必须证明虚拟表中的函数指针具有特定值.为了在C++中进行虚拟化,编译器必须证明该对象是特定类的实例.在第一种情况下,证据似乎更难.但是,虚拟表通常仅在极少数地方进行修改,最重要的是:仅仅因为它看起来更难,并不意味着编译器不是那么好(否则你可能会认为xoring通常比添加两个更快)整数).

Pup*_*ppy 9

不同之处在于,在C++中,编译器可以保证虚拟表地址永远不会改变.在C中,它只是另一个指针,你可能会对它造成任何破坏.

但是,虚拟表通常仅在极少数地方进行修改

编译器在C 中不知道.在C++中,它可以假设它永远不会改变.

  • @ccom:你在该TU之外传递指针或引用的那一刻,你使编译器几乎不可能知道这一点.这意味着在C中,如果在其他地方定义"成员函数",则会失去该保证,除非编译器非常智能.在C中,编译器必须*证明*你永远不会改变它.在C++中,编译器可以*假设它永远不会改变. (3认同)

Moo*_*uck 3

是的,如果编译器可以推断出虚拟化类型的确切类型,它就可以“去虚拟化”(甚至内联!)调用。只有当编译器能够保证无论如何这都是所需的函数时,编译器才能做到这一点。
主要关注的基本上是线程。在 C++ 示例中,即使在线程环境中,这些保证也成立。在 C 中,这是无法保证的,因为该对象可能会被另一个线程/进程抓取,并被覆盖(故意或以其他方式),因此该函数永远不会“去虚拟化”或直接调用。在 C 中,查找总是存在的。

struct A {
    virtual void func() {std::cout << "A";};
}
struct B : A {
    virtual void func() {std::cout << "B";}
}
int main() {
    B b;
    b.func(); //this will inline in optimized builds.
}
Run Code Online (Sandbox Code Playgroud)