首页 > 解决方案 > 指向类成员函数的函数指针与任意函数指针之间的区别

问题描述

我正在尝试测试调用函数指针以绕过模板以获得有限数量的参数的最快方法。我写了这个基准: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_DIRECTGodbolt 代码中)通过索引到指向类成员函数的函数指针数组来调用模板化函数,该类成员函数定义为

   /* 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++assemblyx86-64function-pointersmember-function-pointers

解决方案


C++ 指向成员函数的指针必须能够指向非虚函数或虚函数。在典型的 vtable/vptr 实现中,调用虚函数涉及从对象表达式中的 vptr 中找到正确的代码地址,并可能将偏移量应用于对象参数地址。

g++ 使用Itanium ABI,因此程序集foo_direct解释访问的指针到成员函数值,如第 2.3 节所述。如果函数是虚拟的,它会通过对象表达式的 vptr 找到代码地址,如果不是虚拟的,则只是从指向成员的指针值中复制代码地址。

我想如果可以看到类类型没有虚函数并且是final. 不过,我不知道 g++ 或其他编译器是否有任何此类优化。


推荐阅读