在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
*/

posted @ 2021-01-11 14:45  iwetuan  阅读(112)  评论(0)    收藏  举报