lz9*_*z96 30 c++ gcc compiler-optimization
#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();
}
Run Code Online (Sandbox Code Playgroud)
使用g ++ - 7编译时-O3 -flto -fdevirtualize-at-ltrans -fipa-pta -fuse-linker-plugin,上述ptr->f()调用不能被虚拟化.
似乎没有外部库可以修改ptr.这是GCC优化器的缺陷,还是因为其他一些来源使得虚拟化在这种情况下不可用?
更新:似乎clang-7 with -flto -O3 -fwhole-program-vtables -fvisibility=hidden是唯一可以虚拟化此程序的编译器+标志(如2018/03).
如果将ptr移动到main函数中,结果非常明确,并提供了一个强有力的提示,说明为什么gcc不想对指针进行去虚拟化.
对此进行反汇编表明,如果'具有静态初始化标志'为false,则初始化静态,然后直接跳回到虚函数调用,即使它之间没有任何可能发生的事情.
这告诉我gcc很难相信任何类型的全局持久性指针必须始终被视为指向未知类型的指针.
事实上,它甚至比这更糟糕.如果添加局部变量,那么f在创建局部变量和调用f与否之间是否发生对静态指针的调用很重要.显示f插入案件的组件在这里:另一个Godbolt链接 ; 并且很容易在网站上重新安排它,看看f一旦另一个呼叫没有插入,组件如何变成内联.
因此,gcc必须假设指针引用的实际类型可能会随着控制流因任何原因离开函数而改变.而且它的声明const是否无关紧要.如果它的地址被采用,或任何其他数量的东西也不相关.
clang做同样的事情.这对我来说似乎过于谨慎,但我不是编译器作家.
我刚刚做了另一个实验,如 Omnifarious 答案中所示。但这一次我让指针指向一个静态对象:
Impl1 x;
static Interface* const ptr = &x ;
Run Code Online (Sandbox Code Playgroud)
GCC对函数调用做了去虚拟化,-O2就足够了。我在 C++ 标准中没有看到任何规则可以使静态存储指针的处理方式与动态存储指针的处理方式不同。
允许更改 指向的地址处的对象ptr。因此,编译器必须跟踪该地址发生的情况,以了解该对象的实际动态类型是什么。所以我的观点是,优化器实现者可能认为在实际程序中跟踪堆上发生的事情太困难,因此编译器不会这样做。