c++ - 指向类成员函数的函数指针与任意函数指针之间的区别
问题描述
我正在尝试测试调用函数指针以绕过模板以获得有限数量的参数的最快方法。我写了这个基准:https ://gcc.godbolt.org/z/T1qzTd
我注意到指向类成员函数的函数指针有很多额外的开销,我无法理解。我的意思是:
结构 bar 和函数 foo 定义如下:
template<uint64_t r>
struct bar {
template<uint64_t n>
uint64_t __attribute__((noinline))
foo() {
return r * n;
}
// ... function pointers with pointers to versions of foo below
第一个选项(在#define DO_DIRECT
Godbolt 代码中)通过索引到指向类成员函数的函数指针数组来调用模板化函数,该类成员函数定义为
/* all of this inside of struct bar */
typedef uint64_t (bar::*foo_wrapper_direct)();
const foo_wrapper_direct call_foo_direct[NUM_FUNCS] = {
&bar::foo<0>,
// a bunch more function pointers to templated foo...
};
// to call templated foo for non compile time input
uint64_t __attribute__((noinline)) foo_direct(uint64_t v) {
return (this->*call_foo_direct[v])();
}
然而,为此的程序集似乎有很多绒毛:
bar<9ul>::foo_direct(unsigned long):
salq $4, %rsi
movq 264(%rsi,%rdi), %r8
movq 256(%rsi,%rdi), %rax
addq %rdi, %r8
testb $1, %al
je .L96
movq (%r8), %rdx
movq -1(%rdx,%rax), %rax
.L96:
movq %r8, %rdi
jmp *%rax
我很难理解。
相比之下,#define DO_INDIRECT
方法定义为:
// forward declare bar and call_foo_wrapper
template<uint64_t r>
struct bar;
template<uint64_t r, uint64_t n>
uint64_t call_foo_wrapper(bar<r> * b);
/* inside of struct bar */
typedef uint64_t (*foo_wrapper_indirect)(bar<r> *);
const foo_wrapper_indirect call_foo_indirect[NUM_FUNCS] = {
&call_foo_wrapper<r, 0>
// a lot more templated versions of foo ...
};
uint64_t __attribute__((noinline)) foo_indirect(uint64_t v) {
return call_foo_indirect[v](this);
}
/* no longer inside struct bar */
template<uint64_t r, uint64_t n>
uint64_t
call_foo_wrapper(bar<r> * b) {
return b->template foo<n>();
}
有一些非常简单的组装:
bar<9ul>::foo_indirect(unsigned long):
jmp *(%rdi,%rsi,8)
我试图理解为什么DO_DIRECT
使用函数指针直接指向类成员函数的方法有这么多绒毛,以及如果可能的话,我如何更改它以去除绒毛。
注意:我__attribute__((noinline))
只是为了更容易检查程序集。
谢谢你。
ps 如果有更好的方法将运行时参数转换为模板参数,我将不胜感激链接示例/手册页。
解决方案
C++ 指向成员函数的指针必须能够指向非虚函数或虚函数。在典型的 vtable/vptr 实现中,调用虚函数涉及从对象表达式中的 vptr 中找到正确的代码地址,并可能将偏移量应用于对象参数地址。
g++ 使用Itanium ABI,因此程序集foo_direct
解释访问的指针到成员函数值,如第 2.3 节所述。如果函数是虚拟的,它会通过对象表达式的 vptr 找到代码地址,如果不是虚拟的,则只是从指向成员的指针值中复制代码地址。
我想如果可以看到类类型没有虚函数并且是final
. 不过,我不知道 g++ 或其他编译器是否有任何此类优化。
推荐阅读
- css - 理解 CSS 变换中的缩放后翻译
- android-studio - 像在 Android Studio 中一样在 vscode 中颤动代码格式
- c - 如何在C中检查指针的值
- javascript - Javascript getUserMedia 在 iphone safari 中首次加载时显示黑屏
- node.js - 使用 Axios 发送请求时出现“连接 ECONNREFUSED”错误
- local-storage - 尝试保存表单字段中的输入
- flutter - 如何将宽度设置为下拉按钮项目
- google-apps-script - 如何让我的电子表格每天自动添加一个数据点?
- html - 具有类“navbar-fixed-top”的引导导航栏未保持固定
- html - CSS视差效果与背景图像比例?