#include <cstdio>
#include <cstdlib>
struct Interface {
virtual void f() = 0;
};
struct Impl1: Interface {
void f() override {
std::puts("foo");
}
};
// or __attribute__ ((visibility ("hidden")))/anonymous namespace
static Interface* const ptr = new Impl1 ;
int main() {
ptr->f();
}
使用g ++ - 7 -O3 -flto -fdevirtualize-at-ltrans -fipa-pta -fuse-linker-plugin
编译时,上述ptr->f()
调用无法进行虚拟化。
似乎没有外部库可以修改ptr
。这是GCC优化器的缺陷,还是因为其他一些来源使得虚拟化在这种情况下不可用?
答案 0 :(得分:9)
如果将ptr移动到main函数中,结果非常明确,并提供了一个强有力的提示,说明gcc为什么不想对指针进行去虚拟化。
disassembly for this表示如果&#39;具有静态初始化标记&#39;是假的,它初始化静态然后跳回到虚函数调用,即使它之间没有任何可能发生的事情。
这告诉我gcc很难相信任何类型的全局持久性指针必须始终被视为指向未知类型的指针。
事实上,它甚至比这更糟糕。如果添加局部变量,那么在创建局部变量和调用f
之间是否发生对静态指针的f
的调用很重要。显示f
插入案例的程序集位于:Another godbolt link;并且很容易在网站上自行重新排列,以便在其他呼叫未被插入后,查看汇编如何变为f
的内联。
因此,gcc必须假设当控制流出于任何原因离开函数时,指针引用的实际类型可能会改变。而且它是否宣布const
是无关紧要的。如果它的地址被采用或其他任何东西也不相关。
clang做同样的事情。这对我来说似乎过于谨慎,但我不是编译器编写者。
答案 1 :(得分:4)
我刚做了另一个实验,就像在Omnifarious的回答中一样。但是这次我将指针指向一个静态对象:
Impl1 x;
static Interface* const ptr = &x ;
GCC do devirtualize the function call,-O2
就足够了。我没有看到C ++标准中的任何规则会使指向静态存储的指针与指向动态存储的指针区别对待。
允许更改ptr
指向的地址处的对象。因此编译器必须跟踪该地址发生的事情,以了解对象的实际动态类型。所以我的观点是,优化器实现者可能已经认为在实际程序中跟踪堆上发生的事情会太困难,所以编译器就不会这样做。