LTO、去虚拟化和虚拟表
LTO, Devirtualization, and Virtual Tables
比较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("%dn", obj.vtab->f());
}
我的GCC不会内联f,尽管它是直接调用的,即de虚拟化。c++中的等效符
class A
{
public:
virtual int f() = 0;
};
class B
{
public:
int f() {return 5;}
};
int main()
{
B b;
printf("%dn", b.f());
}
甚至内联f。所以这是C和c++之间的第一个区别,尽管我不认为c++版本中添加的语义在这种情况下是相关的。
更新2:为了在C中反虚拟化,编译器必须证明虚表中的函数指针具有一定的值。为了在c++ 中去虚拟化,编译器必须证明对象是特定类的实例。在第一种情况下,证明似乎更难。然而,虚拟表通常只在很少的地方被修改,而且最重要的是:仅仅因为它看起来更难,并不意味着编译器在这方面不够好(否则您可能会争辩说xoring通常比两个整数相加要快)。
不同的是,在c++中,编译器可以保证虚表地址永远不会改变。在C语言中,它只是另一个指针,你可以用它造成任何形式的破坏。
然而,虚拟表通常只在很少的地方被修改
编译器不知道C中的。在c++中,它可以假设永远不会改变。
我试图在http://hubicka.blogspot.ca/2014/01/devirtualization-in-c-part-2-low-level.html中总结为什么通用优化很难去虚拟化。你的测试用例对我来说是与GCC 4.8.1内联的,但在稍微不那么琐碎的测试用例中,你将指针传递给main之外的"对象",它不会。
原因是为了证明obj中的虚表指针和虚表本身没有改变,别名分析模块必须跟踪所有可能指向它的位置。在不重要的代码中,如果您在当前编译单元之外传递东西,这通常是失败的。
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算法函数时,如sort
或for_each
)。
- 虚拟决赛作为安全
- PowerPC ppc64le上的Gcc Woverloaded虚拟错误
- 如何在C++中获得"静态纯虚拟"功能?
- C++无法定义虚拟函数 OUTER 类和头文件
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 尝试将unique_ptrs推送到向量时使用纯虚拟函数错误
- 有没有比在库中添加一个并非由所有派生类实现的新虚拟函数更好的设计实践
- 大小虚拟继承中的派生类
- 链接器找不到在虚拟类 c++ 中访问的静态字段的符号
- 使用 C++ 和 i2c 工具从虚拟 i2c 写入和读取
- 重载 -> shared_ptr 个实例中的箭头运算符<interface>,接口中没有纯虚拟析构函数
- 如果整个应用程序是虚拟映射的,为什么 new 会进行系统调用?
- 跨 DLL 边界访问虚拟方法是否安全/可能?
- std::is_trivially_copyable_v 关于虚拟功能
- 删除C++继承中虚拟类成员的代码重复
- 子类地址等于虚拟基类地址?
- 当覆盖存在时调用基本虚拟"binded to object"函数
- 用于创建/注册虚拟存储设备的 IOKit 驱动程序
- LTO、去虚拟化和虚拟表