Where is the virtual table pointer

Virtual table pointer

Someone in the group asked. I wrote it

Starting from the simplest class, there are differences between single inheritance and multi inheritance virtual table pointers

Code in 32 environment:

Write the conclusion first:

          Virtual table pointer is assigned in constructor and destructor (disassembly proof below), assignment: * this = virtual table pointer (i.e. the first member)

          The virtual table pointer points to an array to store virtual functions, which are sorted according to the general declaration

          This array is usually in. rdata or. data

          Each parent class will set its own virtual table in the construction and destruction, which will be written in the following inheritance

        * Why should we assign values in both construction and destruction? It will be written in the following inheritance
 

The following code comes with some memory layouts

Virtual table pointer in constructor * this = virtual table pointer, pointing to array [virtual void t]

The member variable a is at this + 4 and follows the virtual table. If there is no virtual table, it will be placed on this

#include <iostream>
#include <Windows.h>
#include <stdio.h>
using std::cout;
using std::endl;

class A
{
public:
	A() {		
		a = 1;
	}
	~A() {
	}
	virtual void t() {
		cout << "t" << endl;
	}
	int a;
};

int main()
{
	cout <<"size:" << sizeof(A) << endl;
	A a;
	DWORD * ptr = (DWORD*)&a;
	DWORD vptr_addr = *ptr;
	//The virtual table is at the address of the first member, * this
	cout << "Virtual table:" << std::hex << vptr_addr<< endl;

	// Because vptr occupies the position of the first member, member a follows the virtual table
	int member = *(ptr + 1);
	cout << "member:" << member << endl;

	//Find the function address through the virtual table. There is only one virtual function, so the offset is 0
	DWORD * arr_func = (DWORD*)vptr_addr;
	void (*func)() = (void (*)())(*(arr_func + 0)); // arr_func[0]
	cout << "Function address:" << func << endl;
	func();

	return 0;
}

Look at the construction destructor of disassembly, delete irrelevant ones, and only look at the assignment of virtual table pointer in construction and Deconstruction:

In these two functions, the assignment is related to inheritance to prevent calling the wrong virtual function

A(){

mov         dword ptr [this],ecx   // ecx is this
mov         eax,dword ptr [this]   // eax = this

// *This = virtual table pointer of a, assignment here
mov         dword ptr [eax],offset A::`vftable' (0419B34h) 
    
}

Virtual table pointer: 0x00419B34
 Memory data: 0x00419B34  9a 11 41 00 00 00 00 00
 Restore array: [0x0041119a] -> [virtual t]


// It also appears in the destruct
~A(){

mov         dword ptr [this],ecx   // ecx == this
mov         eax,dword ptr [this]   // eax = this

// *This = virtual table pointer of a
mov         dword ptr [eax],offset A::`vftable' (0419B34h)
}

vptr here is 0x00419B34, the first function address is 0x0041119a - > the address of virtual function t;

Single inheritance:

The parent class will point its own virtual table pointer of * this = parent class to its own array in the constructor, [virtual ~A, virtual A::t]

The subclass will put * this in the construction   = Virtual table pointer of subclass, array [virtual ~B, virtual A::t]

Virtual functions are generally sorted in the order of declaration in the array

Data members are arranged in succession order, i.e. virtual tables, a and B

Set * this = parent virtual table in parent class construction and destruct to prevent errors in calling virtual functions

Now suppose:

        1. Subclass, if any   virtual void t, then subclass virtual table array: [virtual ~B, virtual B::t]

        two   If t() is called during parent class construction and destruction, and * this = parent virtual table is not set,

            The parent class will call the virtual void t of the child class

        

#include <iostream>
#include <Windows.h>
#include <stdio.h>
using std::cout;
using std::endl;

class A
{
public:
	A() {		
		a = 1;
	}

	virtual ~A() { // Virtual deconstruction
	}

	virtual void t() {
		cout << "t" << endl;
	}
	int a;
};
class B : public A
{
public:
	B() {
		b = 2;
	}
	~B() {

	}
	int b;
};

int main()
{
	cout <<"size:" << sizeof(B) << endl;
	B b;
	DWORD * ptr = (DWORD*)&b;
	DWORD vptr_addr = *ptr;
	cout << "Virtual table:" <<std::hex <<vptr_addr << endl;

	//The reason for obtaining members and inheriting order: A's things are put in the front and his own things are put in the back
	int member_in_class_a = *(ptr + 1);
	int member_in_class_b = *(ptr + 2);
	cout << "A::a :" << member_in_class_a << ", B::b:" << member_in_class_b << endl;

	// Get the virtual table pointer of subclass and call function T. the first is destructor and the second function is t
	DWORD * arr_func = (DWORD*)vptr_addr;
	void(*func)() = (void(*)())(arr_func[1]);
	cout << "Function address:" << std::hex <<func << endl;
	func();


	return 0;
}

Disassembly, constructor, remove irrelevant:

A(){
mov         dword ptr [this],ecx  // ecx = this pointer
mov         eax,dword ptr [this]  // eax = this

// *this = parent virtual table pointer 0x00419B34
mov         dword ptr [eax],offset A::`vftable' (0419B34h) 
}

A Virtual table address of: 0x00419B34  
A Virtual table memory data:0x00419B34  65 14 41 00 60 14 41 00
 Restore array:[0x00411465,0x00411460] -> [virtual ~A , virtual A::t ]
corresponding VSdebug:
0x00411465 {virtualptr.exe!A::`vector deleting destructor'(unsigned int)} // Destruct ~ A
0x00411460 {virtualptr.exe!A::t(void)} // A::t


B() {

mov         ecx,dword ptr [this]    // eax = this
call        A::A (041146Fh)         // Call parent class construction
mov         eax,dword ptr [this]    // eax = this

// *this = subclass virtual table pointer 0x00419B44
mov         dword ptr [eax],offset B::`vftable' (0419B44h)  

}

B Virtual table address of:0x00419B44
 Virtual table memory data:0x00419B44  51 14 41 00 60 14 41 00 
array:[0x00411451, 00411460] -> [virtual ~B , virtual A::t]
corresponding VS debug:
0x00411451 {virtualptr.exe!B::`vector deleting destructor'(unsigned int)} // Destruct ~ B
0x00411460 {virtualptr.exe!A::t(void)}	// A::t

You can see:

The parent virtual table points to its own array: [virtual ~A, virtual A::t]

Subclass points to its own array: [virtual ~B, virtual A:t]

Prevent the parent class from calling subclass virtual functions

Destructor disassembly is also the same operation to prevent subclass virtual functions from being destructed.

~B() {
mov         dword ptr [this],ecx  // ecx == this
mov         eax,dword ptr [this]  // eax  = this 

// *This = virtual table pointer of B
mov         dword ptr [eax],offset B::`vftable' (0419B44h)  

// ecx = this
mov         ecx,dword ptr [this]  

// Call parent class destructor
call        A::~A (041144Ch)  
}


~A(){
mov         dword ptr [this],ecx  // ecx == this

// eax = this
mov         eax,dword ptr [this]  

// *This = virtual table of a
mov         dword ptr [eax],offset A::`vftable' (0419B34h)  
}

Do a test?

Now suppose there is another base class:

class Base {
public:
	Base() {
		base = 111;
	}
	virtual ~Base() {
	}
	int base;
};

class A : public Base{
//Same as above
}
class B: public A{
//Same as above
}

At this time, there will be three virtual tables, and the construction and destruction of Base will also assign a value to Base's own virtual table;

Base virtual table - > [virtual ~ base]

A virtual table - > [virtual ~ A, virtual A:: T]

B virtual table - > [virtual ~ B, virtual A:: T]

Memory layout, as mentioned above, is arranged by inheritance:

First address: virtual table, Base::base, A::a, B::b

If Base is changed to this, there is no virtual function:

class Base {
public:
	Base() {
		base = 111;
	}
	~Base() {
	}
	int base;
};

At this time, the. Base has no virtual table, and its construction and destruction will not assign its own virtual table

  A virtual table and B virtual table remain unchanged

The memory layout remains the same

For example:

If
int main(){
    B b;  // &b ==  0x0018FEE4

    b Memory data for
    0x0018FEE4  48 9b 41 00 6f 00 00 00 01 00 00 00 02 00 00 00
    
    vs debug Corresponding data:
+		__vfptr	0x00419b48 {virtualptr.exe!void(* B::`vftable'[3])()} 
		base	0x0000006f	int
		a	0x00000001	int
		b	0x00000002	int
}



Multiple inheritance:

Keywords: asm Cpp WIN32

Added by cyberdwarf on Wed, 24 Nov 2021 01:06:27 +0200