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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么gcc不能虚拟化这个函数调用?相关的知识,希望对你有一定的参考价值。

#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

答案

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

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

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

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

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

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

另一答案

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

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

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

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

以上是关于为什么gcc不能虚拟化这个函数调用?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能从 gcc 中的前身模板化成员函数访问祖先方法?

为啥这个自定义分配器的析构函数在 GCC/MSVS 的 stdlib 中被调用两次

GCC Devirtualization and Inlining only first Interface

让 gcc 在 c 操作之间调用特定的函数

GCC 生成的程序集 - C 函数调用时的段错误

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段