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

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优化器的缺陷,还是因为其他一些来源使得虚拟化在这种情况下不可用?

Godbolt链接

更新:似乎clang-7 with -flto -O3 -fwhole-program-vtables -fvisibility=hidden是唯一可以虚拟化此程序的编译器+标志(如2018/03).

Omn*_*ous 9

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

对此进行反汇编表明,如果'具有静态初始化标志'为false,则初始化静态,然后直接跳回到虚函数调用,即使它之间没有任何可能发生的事情.

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

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

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

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

  • @HadiBrais - 这个评论太大了.并且唯一可以给你真正答案的人.我建议提交错误报告,以便错过优化机会.我自己填补了这些,他们被认真对待. (4认同)
  • 这应该是评论,而不是答案.答案应该足够详细地解决以下问题:优化是否合法?在编译器中实现优化有多难?为什么GCC不执行优化?值得实施吗? (2认同)

Oli*_*liv 5

我刚刚做了另一个实验,如 Omnifarious 答案中所示。但这一次我让指针指向一个静态对象:

Impl1 x;
static Interface* const ptr = &x ;
Run Code Online (Sandbox Code Playgroud)

GCC对函数调用做了去虚拟化-O2就足够了。我在 C++ 标准中没有看到任何规则可以使静态存储指针的处理方式与动态存储指针的处理方式不同。

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

  • @HadiBrais 老实说,我犹豫是否将其放入对 Omnifarious 答案的评论中,因为它的答案因该实验而无效。另一方面,这也不是一个恰当的评论。当我能够证明之前的答案缺少某些东西而我仍然无法提供完美的答案时,我已经养成了给出部分答案的习惯。 (3认同)
  • @HadiBrais我发现评论不可读,我更喜欢类似维基讨论页面的内容,这样可以通过协作找到解决方案。(那是海里的瓶子)。 (3认同)