gpt4 book ai didi

c++ - GCC 和 Clang 都不会通过编译时已知的函数指针数组进行内联调用——为什么?

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:50:13 25 4
gpt4 key购买 nike

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 和 Clang 提供的语言扩展属性 always_inline 也没有任何区别。

最佳答案

编译器无法内联 foo1 中的调用,因为该调用未使用编译时常量被调用方。如果它知道在编译时通过内联将常量参数传递给 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 个不同的调用开始,这些调用具有常量被调用者,所有这些调用都是内联的。然后它进一步优化生成的代码,如果它认为有利可图,则生成自己的跳转表。

我想编译器可能会尝试从您的跳转表中提取一个开关,然后内联所有内容,但这会非常复杂并且在一般情况下不太可能产生性能改进,因此 gcc 和 clang 似乎都不会这样做.

关于c++ - GCC 和 Clang 都不会通过编译时已知的函数指针数组进行内联调用——为什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44614201/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com