Where does virtual method table store in C++?

2019-08-09 04:58发布

问题:

I want to know how does class object (not instances, but exactly classes) store in memory?

class A {
public:
    int a;
    virtual void f();
    virtual ~A();
};

class B : public A {
public:
    int b;
    void f() final override;
};

I know, that usually (not strongly described by standard) in case of this inheritance (B derived from A) we have:

memory: ....AB...

where AB is a class object of B (if I understand it correctly). If we go deeper (tried with clang and gcc), we can see something like (again, not strongly described in standard):

A
    vtptr*
    int a
B
    vtptr*
    int b

Okay, now we see where do the a and b properties store. And we also see the pointer to virtual method table. But where does vtptr* (virtual method table) actually store? Why not near with the classes? Or it does?

Also, here is another question: I was able to change virtual method tables by changing the pointers (simple logic). Can I also change a pointer to it's methods safely?

P.S. In your questions you may answer for gcc and clang. P.P.S. If I am wrong somewhere please point it too in your answers.

回答1:

The C++ standard does not prescribe how the virtual function mechanism should be implemented. In practice all C++ implementations use a virtual function table per class, and a virtual function table pointer in each object of class with virtual functions (called a polymorphic class). Yet the details can differ, in particular for multiple inheritance and virtual inheritance.

You can read about the common choices in Stanley Lippman's classic book Inside The C++ Object Model.

It doesn't make much sense to ask “where” a virtual function table is stored. It's much like any static variable: its location depends on the implementation and is pretty much arbitrary. And regarding

Why not near with the classes?

… classes as such are not stored anywhere, they are not objects, so this doesn't make sense, sorry.

You can ask more meaningfully where is the vtable pointer stored in each object, for a given implementation?

And usually that's at the start of the object, but if you derive from a non-polymorphic class, and add a virtual function, then you might get the vtable pointer somewhere else. Or not. The latter possibility is much of the reason why a static_cast of Derived* to Base* (or vice versa) can do an address adjustment, i.e. is different from a simple reinterpret_cast.



回答2:

Read the wikipage on virtual method table.

Where is the vtable (itself) stored is implementation specific (compiler, linker, operating-system specific). But it is often stored (like literal strings are) in the code segment of your executable. So objects generally (i.e. without multiple inheritance) start with a _vptr pointer pointing to their vtable. With multiple or virtual inheritance you could have several vtable pointers.

As commented, you should not care about these details. If you really care, ask your compiler to dump internal representations or emitted assembly code. (e.g. compile with g++ -fdump-tree-all -fverbose-asm -S)



回答3:

But where does vtptr* (virtual method table) actually [point]? Why not near with the classes? Or it does?

It could be anywhere... who cares? The way it works it's often implemented is a bit like this... imagine there's a hidden static member for class A:

VDT A::vdt = {
    { address of A::f code, 
      address of A::~A code },
    miscellaneous type-specific information needed for dynamic cast etc.
};

The exact layout's unknown, but there could well be an array of addresses of virtual member functions. As with any static information, the address is unrelated to the address of any given object instance... the pointers in the objects to the virtual dispatch table are there to allow this decoupling.

Also, here is another question: I was able to change virtual method tables by changing the pointers (simple logic). Can I also change a pointer to it's methods safely?

This is not safe, and even if it ostensibly works sometimes it may not be honoured consistently (e.g. in situations where the compiler's able to determine the specific override to call at compile time, it may bypass the runtime virtual dispatch table consultation completely).