c++虚表学习2

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++虚表学习2相关的知识,希望对你有一定的参考价值。

前文

  1. 本文会让读者明白虚表原理,
  2. 理解类的大致内存结构。棱形继承下内存布局,和虚继承单一内存布局情况。
  3. 父类构造函数调用虚函数会怎么样
  4. 父类析构函数调用虚函数情况
  5. 为什么析构函数一定要是虚函数

单继承

我们首先参阅如下的代码:

class Person 
public:
	int pFlag = 2;
	Person() 
		printf("Person \\r\\n");
	
	virtual ~Person() 
		printf(" ~Person \\r\\n");
	
	virtual void vSayPerson() 
		printf("Person \\r\\n");
	

	void nSayPerson() 
		printf("nSayPerson \\r\\n");
	
;
class XH :public Person 
public:
	int xhFlag = 4;
	virtual ~XH() 
		printf("~XH \\r\\n");
	 
	XH() 
		printf("XH \\r\\n");
	
	virtual void vSayXH() 
		printf("vSayXH \\r\\n");
	
	virtual void vSayXH2() 
		printf("vSayXH2 \\r\\n");
	
	void nSayXH() 
		printf("nSayXH \\r\\n");
	

;
int main()

	
		XH* pXh = new XH();
		pXh->nSayPerson();
		pXh->vSayPerson();
		pXh->vSayXH2();
		pXh->nSayXH();
		delete pXh;
	
	

	return 0;


Debug编译后的汇编代码


我们跟进到构造函数中:

我们按照上面的顺序逐个分析

  1. 调用父类的构造函数。因为子类可能会用到父类的东西
  2. 给自己虚表赋值,注意虚表在内存首地址。
  3. 给自己变量赋值 。在上面你注意 [eax+8] 这个地址不是eax+4,证明中间还有其他东西。这里存放的是父类的局部变量
  4. 调用自身构造方法内的函数体

我们首先构建出整个内存图:

构造函数要先调父类的初始化函数:
因为子类会用到父类资源,比如子类获取父类的变量

先初始化虚表指针在调用属性初始化和方法体:
因为构造函数会有可能调用虚函数

先初始化属性在调用方法体:
方法体可能会获取属性

我们首先虚表的赋值代码:

我们接下来看下Person这个类的初始化函数



你会差异的发现父类构造也会填入自己虚表,完成父类构造的后,子类又会覆盖写入这个虚表地址。
这样会有什么关系和异常呢?假设子类重写父类的虚函数,在父类构造函数调用虚函数只会调用自己的函数而不是子类的。

我们看下XH虚表地址的交叉引用信息

我们观察下XH析构函数:

为啥需要在自己析构函数中再次给自己的虚表赋值呢?为了解答这个答案我们首先才看~Person析构


在析构对象流程,首先释放子类的所有子类资源,在释放父类所有资源。因为子类资源被释放了,如果调用到父类时虚表没有还原父类的虚表,那么父类析构中有调用虚函数的可能会引起意外的异常。因为指向的函数是一个释放资源的子类函数。

我们最后看看几个虚函数和非虚函数的调用

	   pXh->nSayPerson();
		pXh->vSayPerson();
		


首先我们要知道的是XH的虚表中第二项就是vSayPerson函数地址,第一项是析构函数代理函数地址。

现在你应该对虚函数的调用有一定的认识了吧。现在你应该举一反三回答出为啥析构函数为啥一定要是虚函数了。。。

我们现在重写Person虚函数看看XH的虚表会怎么样.


class XH :public Person 
public:
	int xhFlag = 4;
	virtual ~XH() 
		printf("~XH \\r\\n");
	
	XH() 
		printf("XH \\r\\n");
	
	virtual void vSayXH() 
		printf("vSayXH \\r\\n");
	
	virtual void vSayXH2() 
		printf("vSayXH2 \\r\\n");
	
	void nSayXH() 
		printf("nSayXH \\r\\n");
	
	//重写
	virtual void vSayPerson() 
		printf("XH vSayPerson\\r\\n");
	
;

多继承

class BaseClass 
public:
	int baseFlag = -1;
	int fill[32] = 0;
	BaseClass(int flag) 
		baseFlag = flag;
		printf("BaseClass \\r\\n");
	
	virtual ~BaseClass() 
		printf(" ~BaseClass \\r\\n");
	
;
class Person :  public  BaseClass 
public:
	int pFlag = 2;
	Person() :BaseClass(2) 
		printf("Person \\r\\n");
	
	virtual ~Person() 
		printf(" ~Person \\r\\n");
	
	virtual void vSayPerson() 
		printf("Person \\r\\n");
	

	void nSayPerson() 
		printf("nSayPerson \\r\\n");
	
;

class Female : public  BaseClass 
public:
	int fFlag = 3;
	Female() :BaseClass(3) 
		printf("Female \\r\\n");
	

	virtual ~Female() 
		printf(" ~Female \\r\\n");
	
	virtual void vSayFemale() 
		printf("vSayFemale \\r\\n");
	

	void nSayFemale() 
		printf("nSayFemale \\r\\n");
	
;




class XM :public  Person, public Female 
public:
	virtual void vSayFemale() 
		printf("vSayFemale \\r\\n");
	

	virtual ~XM() 
		printf("~XM \\r\\n");
	
	XM() 
		printf("XM \\r\\n");
	
	virtual void sayXm() 
		printf("sayXm \\r\\n");
	
;






int main()

	XM* pXm = new XM();
	//280
	printf("%d \\r\\n", sizeof XM);
	delete pXm;
	return 0;




我们可以看到多继承下XM输出类大小是280字节,我们改用虚继承

class BaseClass 
public:
	int baseFlag = -1;
	int fill[32] = 0;
	BaseClass(int flag) 
		baseFlag = flag;
		printf("BaseClass \\r\\n");
	
	virtual ~BaseClass() 
		printf(" ~BaseClass \\r\\n");
	
;
class Person : virtual public  BaseClass 
public:
	int pFlag = 2;
	Person() :BaseClass(2) 
		printf("Person \\r\\n");
	
	virtual ~Person() 
		printf(" ~Person \\r\\n");
	
	virtual void vSayPerson() 
		printf("Person \\r\\n");
	

	void nSayPerson() 
		printf("nSayPerson \\r\\n");
	
;

class Female :virtual  public  BaseClass 
public:
	int fFlag = 3;
	Female() :BaseClass(3) 
		printf("Female \\r\\n");
	

	virtual ~Female() 
		printf(" ~Female \\r\\n");
	
	virtual void vSayFemale() 
		printf("vSayFemale \\r\\n");
	

	void nSayFemale() 
		printf("nSayFemale \\r\\n");
	
;




class XM :public  Person, public Female 
public:
	virtual void vSayFemale() 
		printf("vSayFemale \\r\\n");
	

	virtual ~XM() 
		printf("~XM \\r\\n");
	
	XM():BaseClass(0x123) 
		printf("XM \\r\\n");
	
	virtual void sayXm() 
		printf("sayXm \\r\\n");
	
;






int main()

	XM* pXm = new XM();
	//280
	printf("%d \\r\\n", sizeof XM);
	delete pXm;
	return 0;


普通多继承类内存视图

可见虚继承减少部分内存,我们首先研究下分非虚继承下的XM内存结构

我们直接查看XM的构造函数

析构方法

多继承类内存视图

我们知道虚继承可减少共同父类占用空间,比如本例中XM类会有两个BaseClass类,因此我们会想280减去一个Base类大小就是虚继承后的大小。具体数值为 144=280-136,但是我们通过允许后发现实际内存是160大小。

//..其他代码略,这里是虚继承的代码
int main()

	XM* pXm = new XM();

	printf("sizeof XM %d \\r\\n", sizeof XM);
	printf("sizeof Female %d \\r\\n", sizeof Female);
	printf("sizeof Person %d \\r\\n", sizeof Person);
	printf("sizeof BaseClass %d \\r\\n", sizeof BaseClass);
	delete pXm;
	return 0;


我们很明显发现FemalePerson大小变大了8字节.

为了研究这个问题们修改以下代码首先构造一个Female


int main()

	Female* pXm = new Female();

	printf("sizeof XM %d \\r\\n", sizeof XM);
	printf("sizeof Female %d \\r\\n", sizeof Female);
	printf("sizeof Person %d \\r\\n", sizeof Person);
	printf("sizeof BaseClass %d \\r\\n", sizeof BaseClass);
	delete pXm;
	return 0;

我们这里为方便理解直接给出虚继承内存结构:

IDA PRO 查看构造函数你会发现这个代码会有分支结构,有可能不会调用虚基类初始化函数


我们最后再看看XM这个类的内存结构体:


以上是关于c++虚表学习2的主要内容,如果未能解决你的问题,请参考以下文章

c++虚表学习2

C++学习:3多态

C++多态:从虚表指针到设计模式

C++从虚表地址中取内容

C++多态

C++内存分布 虚表 虚指针(非常重要)