Compiler Explorer上的示例代码:https://godbolt.org/g/fPfw4k
我试图使用函数指针数组作为跳转表而不是开关,因为我觉得它更干净。然而,令我惊讶的是,GCC和Clang编译器似乎都无法强调这一点。
有具体原因吗?
示例代码包含死链接:
namespace{
template<int N>
int bar(){
return N;
}
int foo1(int n){
if(n < 0 || n > 5){
__builtin_unreachable();
}
#if __clang__
__builtin_assume(n >= 0 && n <= 5);
#endif
static int (* const fns[])() = {
bar<0>, bar<1>, bar<2>, bar<3>, bar<4>, bar<5>
};
return fns[n]();
}
int foo2(int n){
#if __clang__
__builtin_assume(n >= 0 && n <= 5);
#endif
switch(n){
case 0:
return bar<0>();
case 1:
return bar<1>();
case 2:
return bar<2>();
case 3:
return bar<3>();
case 4:
return bar<4>();
case 5:
return bar<5>();
default:
__builtin_unreachable();
}
}
}
int main(int argc, char** argv){
volatile int n = foo1(argc);
volatile int p = foo2(argc);
}
使用GCC&amp; amp;提供的语言扩展属性always_inline
。 Clang也没什么区别。
答案 0 :(得分:0)
编译器无法内联foo1中的调用,因为调用不使用编译时常量callee。如果它知道在编译时通过内联将一个常量参数传递给foo1,它将内联正确的函数。
考虑这个例子:
namespace{
template<int N>
int bar(){
return N;
}
int foo1(int n){
if(n < 0 || n > 5){
__builtin_unreachable();
}
#if __clang__
__builtin_assume(n >= 0 && n <= 5);
#endif
static int (* const fns[])() = {
bar<0>, bar<1>, bar<2>, bar<3>, bar<4>, bar<5>
};
return fns[n]();
}
}
int main(int argc, char** argv){
int n = foo1(3);
return n;
}
两个编译器都将其编译为以下代码:
main:
mov eax, 3
ret
在foo2的情况下,编译器以5个不同的调用方式启动,其中包含常量callees,所有这些调用都是内联的。然后它进一步优化生成的代码,如果它认为有利可图,则生成自己的跳转表。
我想编译器可能会尝试从跳转表中提取一个开关,然后内联所有内容,但是这将非常复杂,并且在一般情况下不太可能产生性能提升,因此gcc和clang似乎都没有这样做