为什么编译器在内联时不虚拟化对最终类的调用?

Jan*_*tke 8 c++ clang compiler-optimization devirtualization

struct base {
    virtual void vcall() = 0;
};

struct foo final : base {
    void vcall() final;
};

void call_base(base& b) {
    b.vcall();
}

void call_foo(foo& f) {
    call_base(f);
}

void call_foo_directly(foo& f) {
    f.vcall();
}
Run Code Online (Sandbox Code Playgroud)

clang 16 产生:

call_base(base&):
        mov     rax, qword ptr [rdi]
        jmp     qword ptr [rax]
call_foo(foo&):
        mov     rax, qword ptr [rdi]
        jmp     qword ptr [rax]
call_foo_directly(foo&):
        jmp     foo::vcall()@PLT
Run Code Online (Sandbox Code Playgroud)

GCC 和 MSVC 产生相同的结果,因此这不是仅限于 clang 的问题。是否也应该call_foo包含非虚拟调用foo::vcall()?这是错过的优化,还是调用有可能是虚拟的?

请参阅Compiler Explorer 上的实时示例

SrP*_*nda -1

编译器确实会尝试,但需要内联一些东西,如果一个函数没有实现,它只是一个空调用,这就是编译的内容;添加final只会阻止以后的使用overridevolatile需要通过优化来编译它,这样一切都不会被优化掉。

在bodbolt中运行它。

struct base {
    volatile int num = 111;
    virtual void vcall() = 0;
};

struct foo final : base {
    void vcall() {
        num += 222;
    };
};

void call_base(base& b) {
    b.vcall();
}
void call_foo(foo& f) {
    call_base(f);
}

void call_foo_directly(foo& f) {
    f.vcall();
}

void main_func(void) {
    foo val;
    call_foo(val);
    call_foo_directly(val);
}
Run Code Online (Sandbox Code Playgroud)

这是带有-O3部分反汇编的clang-15(与-O2相同);vs 无法内联call_foo

main_func():                          # @main_func()
        mov     dword ptr [rsp - 8], 111
        add     dword ptr [rsp - 8], 222
        add     dword ptr [rsp - 8], 222
        ret
Run Code Online (Sandbox Code Playgroud)

  • 我没有说这是Java。编译器无法优化当前 TU 中没有定义的任何内容。只要看一下https://godbolt.org/z/G6vWP8vWf,你就会看到`main`中有两个非虚拟调用。您可以看出它是非虚拟的,因为它是直接调用指令“call foo::vcall()@PLT”,因此我们不会调用从 vtable 中获取的地址。 (2认同)