How to obtain a pointer out of a C++ vtable?

前端 未结 5 965
有刺的猬
有刺的猬 2020-12-14 09:15

Say you have a C++ class like:

class Foo {
 public:
  virtual ~Foo() {}
  virtual DoSomething() = 0;
};

The C++ compiler translates a call

相关标签:
5条回答
  • 2020-12-14 09:49

    I can think of two other solutions, rather than digging in the C++ object model.

    The first (and obvious): Generic Programming (aka templates)

    Don't use a base class, refactor the methods that depend on the base class so that they take the "Strategy" as a template argument. This will completely eliminate the virtual calls.

    The second, less obvious, is to reverse the dependencies.

    Instead of injecting the strategy in the algorithm, inject the algorithm in the strategy. This way you will have a single virtual call, at the beginning, and then it will proceed "normally". Templates can help once again here.

    0 讨论(0)
  • 2020-12-14 09:50

    First, class types have a vtable. Instances of that type have a pointer to the vtable. This means that if the contents of the vtable change for a type all instances of that type are affected. But specific instance can have their vtable pointer changed.

    There is no standard way to retrieve the vtable pointer from an instance because it is dependent upon the compiler's implementation. See this post for more details. However, G++ and MSVC++ seem to layout class objects as described on wikipedia. Classes can have pointers to multiple vtables. For the sake of simplicity I'll talk about classes that only have one vtable pointer.

    To get the pointer of a function out of a vtable it can be done as simply as this:

    int* cVtablePtr = (int*)((int*)c)[0];
    void* doSomethingPtr = (void*)cVtablePtr[1];
    

    Where c is an instance of class C for this class definition:

    class A
    {
    public:
        virtual void A1() { cout << "A->A1" << endl; }
        virtual void DoSomething() { cout << "DoSomething" << endl; };
    };
    
    class C : public A
    {
    public:  
        virtual void A1() { cout << "C->A1" << endl; }
        virtual void C1() { cout << "C->C1" << endl; }
    };
    

    The class C is just a struct whose first member is the pointer to a vtable in this case.

    In the case of a JIT compiler it might be possible to cache the lookup in the vtable by regenrating code.

    At first the JIT compiler might produce this:

    void* func_ptr = obj_instance[vtable_offest][function_offset];
    func_ptr(this, param1, param2)
    

    Now that the func_ptr is known the JIT can kill off that old code and simply hard code that function address into the compiled code:

    hardcoded_func_ptr(this, param1, param2)
    

    One thing I should note is while you can overwrite the instances vtable pointer it is not always possible to overwrite the contents of a vtable. For example, on Windows the vtable is marked as read only memory but on OS X it is read/write. So on windows trying to change the contents of the vtable will result in an Access Violation unless you change the page access with VirtualProtect.

    0 讨论(0)
  • 2020-12-14 09:54

    Why do you think &DerivedFoo::DoSomething is different? Isn't this exactly what you're asking for? The way I think about it, any call to DerivedFoo::DoSomething() will call the same function, passing a different this pointer. The vtable merely distinguishes between different types derived from Foo, not instances.

    0 讨论(0)
  • 2020-12-14 09:54

    This is not a direct answer, nor is it necessarily up to date, but it does go into a lot of the details and caveats you need to be aware of when trying to do something like this: http://www.codeproject.com/KB/cpp/FastDelegate.aspx

    No there is not a standard C++ way of doing this. The above is similar to, but not the same as what you're asking for.

    0 讨论(0)
  • 2020-12-14 09:58

    If you call derived->DoSomething(), and DoSomething() is not virtual in the derived class, the compiler should generate a straight call already.

    If you call base->DoSomething(), the compiler has to check one way or another which version of DoSomething() to call, and a vtable is as efficient method as any. If you could guarantee it to always be an instance of the base class, you wouldn't need to make the method virtual in the first place.

    Under select circumstances, it might make sense to do a static_cast before calling a bunch of non-virtual derived methods that are virtual in the base class, but as vtable lookups are common, accounted for, and relatively inexpensive, this definitely falls under the category of premature optimization.

    Templates are another standard C++ means of reusing code without causing a vtable lookup.

    0 讨论(0)
提交回复
热议问题