【问题标题】:Possible to implement bypassing vtable for virtual functions?可以为虚拟功能实现绕过 vtable 吗?
【发布时间】:2014-11-24 20:29:16
【问题描述】:

而不是使用虚拟函数来查找对象中的 vtable 指针,然后它会将您带到包含指向函数的指针的 vtable - 是否不可能仅在对象中包含数据成员哪个直接指向函数?

【问题讨论】:

  • 如果编译器知道对象的静态类型,它可以将调用优化为直接调用。我认为这种优化很常见,但我不是 100% 确定。如果编译器不能做到这一点,那么在对象中拥有自己的指针(以及您需要利用它的任何代码)可能比仅使用 vtable 中的指针的编译器优化得少得多。跨度>
  • 如果在每个对象中添加所有指针,对象的大小会是多少?
  • 这意味着,每个虚拟成员在每个实例中都有一个数据成员。这有点快,但尺寸要大得多。您只为调用保存了一个间接调用,但会浪费大量数据空间。
  • 好吧,我的对象只有一种虚拟接口方法。这个解决方案会更好吗?事实上,你甚至不必存储一个 8 字节的指针,你可以只存储一个数据成员的偏移量。

标签: c++ polymorphism vtable


【解决方案1】:

如果我理解您的问题,您正在寻找一种使用函数指针实现多态性的方法。

嗯,这是可能的,但非常麻烦,容易出错,但在虚函数调用方面很难胜过编译器生成的 de。

怎么做?

这个想法是使用函数指针。为了实现多态,它必须在基类中。

class B {                   // Base class
protected:
    void (B::*fptest)();    // Function pointer to member function
public:
    void mtest()            // Class specific mebmer function
    { cout << "Base test\n"; }  
    void itest()            // Polymorphic function
    { (this->*fptest)(); }        // implemented by calling the poitner to member function 
    B() : fptest(&B::mtest) { }   // Ctor must initialize the function pointer
    virtual ~B() {}        
};

class D : public B {         // Derived class 
public:
    void mtest()             // Class specific mebmer function
    { cout << "Derived test\n"; }
    D()                     // Ctor 
    { fptest = reinterpret_cast<void(B::*)()>(&D::mtest); }  // not sure it's this safe in case of multiple inheritance !!
};

测试这个结构的代码:

B b; 
D d;
B *pb = &b, *pd = &d;
pb->itest(); 
pd->itest();

安全吗?

对此有严格的限制。例如:

  • 您必须确保每个派生类都正确初始化函数指针。
  • 在多重继承的情况下,转换为基类成员的函数指针可能无法按预期工作。
  • 不能重载指针。因此,对于每个可能的签名,您都需要一个不同的指针。这可能很奇怪。

它是否比 vtable 查找更高效?

否:查看为每个对 itest() 的多态调用执行的汇编程序:

; 41   :    pd->itest();  // cod for the call for a derived object
    mov ecx, DWORD PTR _pd$[ebp]        ; load the oject address
    call    ?itest@B@@QAEXXZ            ; call B::itest
; 16   :    void itest() { (this->*fptest)(); }
    push    ebp
    mov ebp, esp
    sub esp, 68                 ; 00000044H
    push    ebx
    push    esi
    push    edi
    mov DWORD PTR _this$[ebp], ecx   ; use address of object as parameter
    mov eax, DWORD PTR _this$[ebp]   ; load the function pointer 
    mov ecx, DWORD PTR _this$[ebp]   ;  "    "
    mov edx, DWORD PTR [eax+4]       ; call the function pointer 
    call    edx
    pop edi
    pop esi
    pop ebx
    mov esp, ebp
    pop ebp
    ret 0

当然,优化器可以内联代码,去掉一些 push 和 pop,但一般原则是生成间接代码。

vtable 查找性能还不够吗?

vtable 查找基本上是从编译器计算的固定偏移量加载函数指针。调用 vitual test() 函数的汇编代码如下所示:

 39   :     pd->test(); 
    mov eax, DWORD PTR _pd$[ebp]
    mov edx, DWORD PTR [eax]
    mov ecx, DWORD PTR _pd$[ebp]
    mov eax, DWORD PTR [edx]
    call    eax

结论

vtable 查找至少与通过函数指针的调用一样高效。编译器负责所有初始化和最复杂的继承情况。更好地使用强大的虚函数,而不是试图手动超越你的编译器。

【讨论】:

  • 如果不开启优化,这个测试并不能真正证明任何有用的东西。
  • @MarkRansom 最好的方法可能是为 OP 的特定情况做一个基准测试,因为您不能从对过度简化的演示代码执行的优化中进行概括。
猜你喜欢
  • 2016-09-24
  • 1970-01-01
  • 1970-01-01
  • 2010-11-26
  • 1970-01-01
  • 2015-01-29
  • 1970-01-01
  • 2019-08-01
  • 2011-01-24
相关资源
最近更新 更多