为什么gcc和clang会为成员函数模板参数生成非常不同的代码?

时间:2016-11-18 12:07:45

标签: c++ templates gcc clang member-function-pointers

我试图理解当成员函数指针用作模板参数时发生了什么。我一直认为函数指针(或成员函数指针)是一个运行时概念,所以我想知道当它们被用作模板参数时会发生什么。因此我查看了此代码生成的输出:

struct Foo { void foo(int i){ } };    
template <typename T,void (T::*F)(int)>
void callFunc(T& t){ (t.*F)(1); }
void callF(Foo& f){ f.foo(1);}    
int main(){
    Foo f;
    callF(f);
    callFunc<Foo,&Foo::foo>(f);
}

其中callF用于比较。 gcc 6.2为两个函数生成完全相同的输出:

callF(Foo&):  // void callFunc<Foo, &Foo::foo>(Foo&):
    push    rbp
    mov     rbp, rsp
    sub     rsp, 16
    mov     QWORD PTR [rbp-8], rdi
    mov     rax, QWORD PTR [rbp-8]
    mov     esi, 1
    mov     rdi, rax
    call    Foo::foo(int)
    nop
    leave
    ret

clang 3.9产生的callF()输出几乎相同:

callF(Foo&):                          # @callF(Foo&)
    push    rbp
    mov     rbp, rsp
    sub     rsp, 16
    mov     esi, 1
    mov     qword ptr [rbp - 8], rdi
    mov     rdi, qword ptr [rbp - 8]
    call    Foo::foo(int)
    add     rsp, 16
    pop     rbp
    ret

但模板实例化的输出非常不同:

void callFunc<Foo, &Foo::foo>(Foo&): # @void callFunc<Foo, &Foo::foo>(Foo&)
    push    rbp
    mov     rbp, rsp
    sub     rsp, 32
    xor     eax, eax
    mov     cl, al
    mov     qword ptr [rbp - 8], rdi
    mov     rdi, qword ptr [rbp - 8]
    test    cl, 1
    mov     qword ptr [rbp - 16], rdi # 8-byte Spill
    jne     .LBB3_1
    jmp     .LBB3_2
.LBB3_1:
    movabs  rax, Foo::foo(int)
    sub     rax, 1
    mov     rcx, qword ptr [rbp - 16] # 8-byte Reload
    mov     rdx, qword ptr [rcx]
    mov     rax, qword ptr [rdx + rax]
    mov     qword ptr [rbp - 24], rax # 8-byte Spill
    jmp     .LBB3_3
.LBB3_2:
    movabs  rax, Foo::foo(int)
    mov     qword ptr [rbp - 24], rax # 8-byte Spill
    jmp     .LBB3_3
.LBB3_3:
    mov     rax, qword ptr [rbp - 24] # 8-byte Reload
    mov     esi, 1
    mov     rdi, qword ptr [rbp - 16] # 8-byte Reload
    call    rax
    add     rsp, 32
    pop     rbp
    ret

为什么? gcc是否采用了一些(可能是非标准的)快捷方式?

2 个答案:

答案 0 :(得分:5)

gcc能够弄清楚模板在做什么,并生成了最简单的代码。铿锵没有。只要可观察结果符合C ++规范,就允许编译器执行任何优化。如果优化掉中间函数指针,那就这样吧。代码中没有其他内容引用临时函数指针,因此可以完全优化它,并用简单的函数调用替换整个事物。

gcc和clang是不同的编译器,由不同的人编写,具有不同的编译C ++的方法和算法。

很自然,预计会看到不同编译器的不同结果。在这种情况下,gcc能够比clang更好地解决问题。我确信在其他情况下clang能够比gcc更好地解决问题。

答案 1 :(得分:3)

此测试在未经任何优化的情况下完成。

一个编译器生成了更详细的未经优化的代码。

未经优化的代码非常简单,无趣。它旨在正确且易于调试,并直接从一些易于优化的中间表示中派生出来。

优化代码的细节是重要的,除非一个荒谬而广泛的减速使得调试变得痛苦。

这里没有任何兴趣可以看到或解释。