1. 程式人生 > 實用技巧 >在MSVC2017的IDE環境中對c++的虛基類函式表的研究

在MSVC2017的IDE環境中對c++的虛基類函式表的研究

??疑問點,為何在獲取VBTable的偏移地址時,在x64中需要使用 int**指標型別呢, 莫非VBTable的實現,在VS中使用的 int[] 型別,而不是跟著x86,x64改變?

// vbptr.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//

#include <iostream>
#include <stdint.h>


struct VB {
	virtual void f1() {
		std::cout << "VB b1" << std::endl;
	};
	int vb;
};

class V1 :virtual public VB {
	virtual void f1() {};
	virtual void f2() {};
	int v1;
};
class V2 :virtual public VB {
	virtual void f1() {};
	virtual void f2() {};
	int v2;
};

class V3 :public V1, public V2 {
	virtual void f1() {
		std::cout << "V3 b1" << std::endl;
	};
	int v3;
};
/* 記憶體佈局
class V3	size(72):
	+---
 0	| +--- (base class V1)
 0	| | {vfptr}
 8	| | {vbptr}
16	| | v1
	| | <alignment member> (size=4)
	| +---
24	| +--- (base class V2)
24	| | {vfptr}
32	| | {vbptr}
40	| | v2
	| | <alignment member> (size=4)
	| +---
48	| v3
	| <alignment member> (size=4)
	+---
	+--- (virtual base VB)
56	| {vfptr}
64	| vb
	| <alignment member> (size=4)
	+---

V3::$vftable@V1@:
	| &V3_meta
	|  0
 0	| &V1::f2

V3::$vftable@V2@:
	| -24
 0	| &V2::f2

V3::$vbtable@V1@:
 0	| -8
 1	| 48 (V3d(V1+8)VB)

V3::$vbtable@V2@:
 0	| -8
 1	| 24 (V3d(V2+8)VB)

V3::$vftable@VB@:
	| -56
 0	| &V3::f1

 關於內部佈局中VTable的說明
 vtable的裡面儲存的有兩個偏移量,
 第一個偏移量表示,從從vbtable所在的地址+偏移量為直接繼承類的例項地址
  從vbtable所在的地址,比如在0x000008
 加上【-8,負號表示方向】個子節 那麼V1【直接繼承類】的地址也就是虛擬函式表的地址則為 0x000008-8 = 0x00000;
 
  第二個偏移量表示,從從vbtable所在的地址+偏移量為直接繼承類的直接繼承類的例項地址
  比如  從vbtable所在的地址,比如在0x000008
  加上48個子節,那麼就是vbtable的實際地址

  例如上圖
  (virtual base VB)的地址在直接繼承類的直接繼承類的偏移地址為56子節,如果取得例項地址,就可以計算偏移地址了
*/

typedef void(*F1)();
using F11 = void(*)(void);
int main()
{
	V3 v;
	//
	std::cout << "vf address"<<  ((int**)&v + 0) << std::endl;
	std::cout << "vb address" << ((int**)&v + 1) << std::endl;

#ifdef _X86_P
	int v1_vbtable = (int)((int**)&v + 1);
	//call 獲取偏移量
	std::cout<< *(*((int**)&v + 1)+0) <<std::endl;
	
	int vb_v1_index2 =(int)(*(*((int**)&v + 1) + 1));
	std::cout<< vb_v1_index2 <<std::endl;
	//std::cout << std::hex<< *((*(int**)&v + 1) + 1) << std::endl;

	int vb_in_v3 = v1_vbtable+ vb_v1_index2;

	//獲取VB的虛擬函式表的位置
	((F11)(**(int**)vb_in_v3))();
#else
	std::cout << "vf address" << ((uint64_t**)&v + 0) << std::endl;
	std::cout << "vb address" << ((uint64_t**)&v + 1) << std::endl;

	uint64_t v1_vbtable = (uint64_t)((uint64_t**)&v + 1);
	//??疑問點,為何在獲取VBTable的偏移地址時,在x64中需要使用 int**指標型別呢, 莫非VBTable的實現,在VS中使用的 int[] 型別,而不是跟著x86,x64改變?
	//call 獲取偏移量,為什麼偏移地址是int**取出來,使用uint64_t**就會出錯呢
	std::cout << *(*((int**)&v + 1) + 0) << std::endl;

	uint64_t vb_v1_index2 = (*(*((int**)&v + 1) + 1));
	std::cout<<std::hex << vb_v1_index2 << std::endl;
	//std::cout << std::hex<< *((*(int**)&v + 1) + 1) << std::endl;

	uint64_t vb_in_v3 = v1_vbtable + vb_v1_index2;

	//獲取VB的虛擬函式表的位置
	((F11)(*(uint64_t*)(*(uint64_t*)vb_in_v3)))();

#endif


	std::cout << "Hello World!\n";
	std::cin.get();
}

/* 輸出結果
x64
vf address000000B4D5D6FC30
vb address000000B4D5D6FC38
vf address000000B4D5D6FC30
vb address000000B4D5D6FC38
-8
30
V3 b1

x86
vf address0117F8E4
vb address0117F8E8
vf address0117F8E4
vb address0117F8E8
-4
18
V3 b1
*/