为什么gcc不能虚拟化这个函数调用?

问题描述 投票:29回答:2
#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优化器的缺陷,还是因为其他一些来源使得虚拟化在这种情况下不可用?

Godbolt link

更新:似乎clang-7 with -flto -O3 -fwhole-program-vtables -fvisibility=hidden is the only compiler+flags (as in 2018/03) that can devirtualize this program

c++ gcc compiler-optimization
2个回答
9
投票

如果将ptr移动到main函数中,结果非常明确,并提供了一个强有力的提示,说明为什么gcc不想对指针进行去虚拟化。

disassembly for this显示如果'has static static initial flag'为false,它会初始化静态然后再跳回到虚函数调用,即使它之间没有任何可能发生的事情。

这告诉我,gcc很难相信任何类型的全局持久性指针必须始终被视为指向未知类型的指针。

事实上,它甚至比这更糟糕。如果你添加一个局部变量,那么静态指针上的f调用是否发生在创建局部变量和调用f之间是否重要。显示f插入案件的集会在这里:Another godbolt link;并且很容易在网站上重新安排它,看看一旦另一个呼叫没有插入,组件如何变成f的内联。

因此,gcc必须假设指针引用的实际类型可能会随着控制流因任何原因离开函数而改变。而且它是否被宣布为const是无关紧要的。如果它的地址被采用,或任何其他数量的东西也不相关。

clang做同样的事情。这对我来说似乎过于谨慎,但我不是编译器作家。


4
投票

我刚做了另一个实验,就像在Omnifarious回答中一样。但这一次我使指针指向一个静态对象:

Impl1 x;
static Interface* const ptr = &x ;

GCC do devirtualize the function call-O2就足够了。我没有看到C ++标准中的任何规则会使指向静态存储的指针与指向动态存储的指针区别对待。

允许更改ptr指向的地址处的对象。因此编译器必须跟踪该地址发生的事情,以了解对象的实际动态类型。所以我的观点是,优化器实现者可能已经考虑过在实际程序中跟踪堆上发生的事情会太困难,所以编译器就是不这样做。

© www.soinside.com 2019 - 2024. All rights reserved.