【问题标题】:How to get "direct" function pointer to a virtual member function?如何获得指向虚拟成员函数的“直接”函数指针?
【发布时间】:2013-12-11 13:42:22
【问题描述】:

我正在一个嵌入式平台上工作,该平台不能很好地处理动态代码(根本没有推测/OOO 执行)。 在这个平台上,我经常在同一个对象上调用虚拟成员函数,但是编译器无法优化 vtable-lookup,因为它似乎没有识别出只有第一次调用才需要查找。

因此我想知道:是否有一种手动方法可以对 C++ 类的虚拟成员函数进行去虚拟化以获得直接指向解析地址的函数指针? 我查看了 C++ 函数指针,但由于它们似乎需要指定类型,我想这不会成功。

提前谢谢你

【问题讨论】:

  • 感谢您指出这可能是重复的,但是另一个线程中的问题没有得到回答:/
  • 当您说“它似乎无法识别仅在第一次调用时才需要查找”时,您的意思是,在单个函数中,保证基类指针始终指向同一个例子? (例如 VBase *b = new Subclass(); b->virtualfunc();...)。

标签: c++ pointers


【解决方案1】:

没有通用的标准 C++ 方法来查找虚函数的地址,只给出对基类对象的引用。此外,没有合理的 type ,因为 this 不需要按照一般约定作为普通参数传递(例如,它可以在寄存器中传递,其他参数在堆栈上) .

但是,如果您不需要可移植性,您可以随时为给定的编译器做任何工作。例如,使用 Microsoft 的 COM(我知道,这不是你的平台),有一个已知的带有 vtable 指针的内存布局,以便从 C 访问功能。

如果您确实需要可移植性,那么我建议设计优化。例如,而不是

class Foo_base
{
public:
    virtual void bar() = 0;
};

喜欢

class Foo_base
{
public:
    typedef (*Bar_func)(Foo_base&);

    virtual Bar_func bar_func() const = 0;
    void bar() { bar_func()( *this ); }
};

支持与以前相同的公共接口,但现在公开内部,可以说,从而允许手动优化对 bar 的重复调用。

【讨论】:

    【解决方案2】:

    关于 gcc,我在调试编译的汇编代码时看到了以下内容。 我已经看到通用方法指针包含两个数据: a) 指向方法的“指针” b)最终添加到类实例起始地址的偏移量(当涉及多重继承时使用偏移量,并且对于第二个和更进一步的父类的方法,如果应用于它们的对象,它们的数据将在不同的起始点)。

    指向方法的“指针”如下: 1)如果“指针”是偶数,则将其解释为普通(非虚拟)函数指针。 2)如果“指针”是奇数,则应减去 1,剩余值应为 0 或 4 或 8 或 12(假设指针大小为 4 个字节)。 前面的编码显然假设所有普通方法都从偶数地址开始(因此编译器应该将它们对齐在偶数地址)。 因此,该偏移量是 vtable 中的偏移量,用于获取“真实”非虚拟方法指针的地址。

    因此,为了使调用去虚拟化,正确的想法是将虚拟方法指针转换为非虚拟方法指针,然后使用它,以便将其应用于作为我们类实例的“主题”。

    下面的代码执行所描述的操作。

    #include <stdio.h>
    #include <string.h>
    #include <typeinfo>
    #include <typeindex>
    #include <cstdint>
    
    struct Animal{
        int weight=0x11111111;
        virtual int mm(){printf("Animal1 mm\n");return 0x77;};
        virtual int nn(){printf("Animal1 nn\n");return 0x99;};
    };
    
    struct Tiger:Animal{
        int weight=0x22222222,height=0x33333333; 
        virtual int mm(){printf("Tigerxx\n");return 0xCC;}
        virtual int nn(){printf("Tigerxx\n");return 0x99;};
    };
    
    typedef int (Animal::*methodPointerT)();
    
    typedef struct {
        void** functionPtr;
        size_t offset;
    } MP;
    
    void devirtualize(methodPointerT& mp0,const Animal& a){
        MP& t=*(MP*)&mp0;
        if((intptr_t)t.functionPtr & 1){
            size_t index=(t.functionPtr-(void**)1); // there is obviously a more
            void** vTable=(void**)(*(void**)&a);        // efficient way. Just for clearness !
            t.functionPtr=(void**)vTable[index];
        }
    };
    
    int main()  
    {
        int (Animal::*mp1)()=&Animal::nn;
        MP& mp1MP=*(MP*)&mp1;
    
      Animal x;Tiger y;
    
        (x.*mp1)();(y.*mp1)();
    
        devirtualize(mp1,x);
    
        (x.*mp1)();(y.*mp1)();
    
    }
    

    【讨论】:

      【解决方案3】:

      是的,这可以通过一种至少适用于 MSVC、GCC 和 Clang 的方式实现。

      我也在寻找如何做到这一点,这是我发现的一篇博客文章,详细解释了它:https://medium.com/@calebleak/fast-virtual-functions-hacking-the-vtable-for-fun-and-profit-25c36409c5e0

      简而言之,这就是您需要做的事情。此函数适用于所有对象:

      template <typename T>
      void** GetVTable(T* obj) {
        return *((void***)obj);
      }
      

      然后要获得指向该类的第一个虚函数的直接函数指针,您可以这样做:

      typedef void(VoidMemberFn)(void*);
      
      VoidMemberFn* fn = (VoidMemberFn*)GetVTable<BaseType>(my_obj_ptr)[0];
      
      // ... sometime later
      
      fn(my_obj_ptr);
      

      所以其实很简单。

      【讨论】:

      • 它可能在您测试的一个架构上与这三个编译器一起工作,但在其他一些架构上肯定会失败。
      • 据我所知,以这种方式设置 VTable 是一个稳定的约定,至少遵循 MSVC、GCC 和 Clang。你能解释一下为什么你说一个特定的架构会导致它失败吗?
      • 在添加自己的答案之前,您是否阅读了已接受答案的第一段?我可以保证 Alf 关于非静态成员函数和普通函数在某些体系结构上调用约定的差异是正确的。在那个非常流行的架构上,您不能通过普通函数指针调用thiscall 调用约定函数,因为“this”指针不会放在正确的寄存器中。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-31
      • 2022-12-01
      • 2010-11-08
      • 2015-12-17
      • 1970-01-01
      相关资源
      最近更新 更多