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

时间:2013-09-29 15:27:17

标签: c++ c++11

现在,我知道内联没有保证,但是......

鉴于以下内容:

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是一个不变的表达式!

3 个答案:

答案 0 :(得分:4)

这里的问题是,在第一种情况下,基于类型的devirtualization将间接调用转换为直接调用。 当您添加成员指针时,不能使用基于类型的devirtualization(因为它通过前端向下传递到有关所调用的类型和虚方法的优化信息,在本例中并非简单地知道)。 GCC可以通过知道virutal table是一个类并且知道只有在构造它之后才能调用它的成员,才能将实际访问权限永久地折叠到B。目前它没有做这样的分析。

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

答案 1 :(得分:1)

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

修改

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

答案 2 :(得分:0)

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

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

这里最具体的是,问题可能是&Derived::f,gcc和clang都有一个特定的表示形式用于指向虚函数的指针:它们使用指向蹦床函数的指针来执行虚拟分辨率。因此,这个trampoline函数可能只是一个嵌套太多而无法让编译器查看。