LTO,虚拟化和虚拟表

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

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

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

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()); } 

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

 class A { public: virtual int f() = 0; }; class B { public: int f() {return 5;} }; int main() { B b; printf("%d\n", bf()); } 

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

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

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

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

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

我试图在http://hubicka.blogspot.ca/2014/01/devirtualization-in-c-part-2-low-level.html中总结为什么generics优化很难进行虚拟化。 你的测试用例通过GCC 4.8.1为我内联,但是在稍微不那么简单的测试用例中,你将指针传递给main中的“对象”,它不会。

原因是为了certificateobj中的虚拟表指针和虚拟表本身没有改变,别名分析模块必须跟踪你可以指向它的所有可能的位置。 在一个非平凡的代码中,你将事物传递到当前编译单元之外,这通常是一个迷失的游戏。

C ++为您提供有关何时可以更改对象类型以及何时更改对象的更多信息。 GCC利用它,它将在下一个版本中更多地使用它。 (我也会很快写下来)。

是的,如果编译器可以推断出虚拟化类型的确切类型,它可以“半虚拟化”(甚至内联!)调用。 编译器只有在能够保证无论如何才能保证这一点时才能执行此操作。
主要关注点基本上是线程化。 在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. } 

这取决于您对编译器内联的比较。 与链接时间或配置文件引导或仅在时间优化中相比,编译器使用的信息较少。 使用较少的信息,编译时优化将更加保守(并且总体上更少内联)。

编译器在内联虚函数时通常仍然相当不错,因为它等同于内联函数指针调用(例如,当您将自由函数传递给STL算法函数时,如sortfor_each )。