调用未内联的常量指针到成员函数

Call to constant pointer-to-member function not being inlined

本文关键字:指针 成员 函数 常量 调用      更新时间:2023-10-16

现在,我知道不能保证内联,但是...

鉴于以下情况:

struct Base {
    virtual int f() = 0;
};
struct Derived : public Base {
    virtual int f() final override {
        return 42;
    }
};
extern Base* b;

我们有:

int main() {
    return static_cast<Derived*>(b)->f();
}

编译为:

main:
    movl    $42, %eax
    ret

还。。。

int main() {
    return (static_cast<Derived*>(b)->*(&Derived::f))();
}

编译为:

main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $16, %esp
    movl    b, %eax
    movl    (%eax), %edx
    movl    %eax, (%esp)
    call    *(%edx)
    leave
    ret

这真的很可悲。

为什么对 PMF 的调用没有内联?PMF 是一个恒定的表达式!

这里的问题是,在第一种情况下,基于类型的去虚拟化将间接调用转换为直接调用。添加成员指针时,不能使用基于类型的去虚拟化(因为它通过前端传递有关所调用的类型和虚拟方法的优化信息来工作,而在这种情况下,这些信息并不容易知道)。 GCC可以通过知道B是一个类并且知道只有在构造它之后才能调用其成员,从而将实际访问不断折叠到virutal table中。 目前它不做这样的分析。

我建议填写增强请求以GCC bugzilla。

并不总是能够内联指向函数的指针(除非编译器能够弄清楚指针实际指向什么,这通常很困难,因此编译器可能会在您期望之前"放弃")。

编辑

扩展我的答案:所有编译器中的首要任务是生成正确的代码(尽管有时也不会发生这种情况!优化,例如内联函数,是编译器只有在"安全"时才会做的事情。编译器可能不太"理解"上面的表达式确实是一个常量表达式,因此回退到"让我们做安全的事情"(即通过虚函数表调用,而不是内联函数)。指向虚拟成员函数的指针在C++中是一个相当棘手的主题。

这确实有点烦人,但并不奇怪。

大多数编译器转换通过模式匹配工作:识别模式并应用转换。那么这可能不会优化的原因是什么?好吧,也许只是没有基于这种模式编写的转换。

最具体地说,问题可能与&Derived::f有关,gcc 和 clang 都使用特定的表示来表示指向虚函数的指针:它们使用指向执行虚拟分辨率的蹦床函数的指针。因此,可能只是因为这个蹦床函数嵌套太多,编译器无法看穿。