为什么 gcc 和 clang 会为成员函数模板参数生成非常不同的代码?
Why does gcc and clang produce very differnt code for member function template parameters?
我想了解将成员函数指针用作模板参数时发生了什么。我一直认为函数指针(或成员函数指针)是一个运行-时间的概念,所以我想知道当它们被用作模板参数时会发生什么。出于这个原因,我看了一下这段代码产生的输出:
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
是在走一些(可能是非标准的)捷径吗?
gcc 能够弄清楚模板在做什么,并生成尽可能简单的代码。 clang 没有。只要可观察的结果符合 C++ 规范,编译器就可以执行任何优化。如果优化掉一个中间函数指针,那就这样吧。代码中没有任何其他内容引用临时函数指针,因此可以将其完全优化掉,整个事情被一个简单的函数调用所取代。
gcc 和 clang 是不同的编译器,由不同的人编写,使用不同的方法和算法来编译 C++。
这是很自然的,并且期望从不同的编译器中看到不同的结果。在这种情况下,gcc 能够比 clang 更好地解决问题。我敢肯定,在其他情况下,clang 能够比 gcc 更好地解决问题。
此测试是在未请求任何优化的情况下完成的。
一个编译器生成了更冗长的未优化代码。
未优化的代码很简单,没有意思。它旨在正确且易于调试,并直接从一些易于优化的中间表示派生。
优化代码的细节才是最重要的,除非出现可笑的、广泛的、使调试变得痛苦的减速。
这里没有什么有趣的东西可以看或解释。
我想了解将成员函数指针用作模板参数时发生了什么。我一直认为函数指针(或成员函数指针)是一个运行-时间的概念,所以我想知道当它们被用作模板参数时会发生什么。出于这个原因,我看了一下这段代码产生的输出:
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
是在走一些(可能是非标准的)捷径吗?
gcc 能够弄清楚模板在做什么,并生成尽可能简单的代码。 clang 没有。只要可观察的结果符合 C++ 规范,编译器就可以执行任何优化。如果优化掉一个中间函数指针,那就这样吧。代码中没有任何其他内容引用临时函数指针,因此可以将其完全优化掉,整个事情被一个简单的函数调用所取代。
gcc 和 clang 是不同的编译器,由不同的人编写,使用不同的方法和算法来编译 C++。
这是很自然的,并且期望从不同的编译器中看到不同的结果。在这种情况下,gcc 能够比 clang 更好地解决问题。我敢肯定,在其他情况下,clang 能够比 gcc 更好地解决问题。
此测试是在未请求任何优化的情况下完成的。
一个编译器生成了更冗长的未优化代码。
未优化的代码很简单,没有意思。它旨在正确且易于调试,并直接从一些易于优化的中间表示派生。
优化代码的细节才是最重要的,除非出现可笑的、广泛的、使调试变得痛苦的减速。
这里没有什么有趣的东西可以看或解释。