92 C++对象模型探索。数据语义学 - 指向成员函数的指针,vcall进一步学习

类指针 调用虚函数的时候,会使用 vptr 找虚函数表。

在使用 函数指针  调用成员虚函数的时候会使用到vcall。如果是vcall代码段,则vcall代码会应道编译器找出正确的虚函数表中的虚函数地址进行调用。

一 指向类成员函数的指针,类静态函数,普通全局函数

从前面的学习我们已经知道如何给 定义一个类成员函数指针了.

那么成员函数也是可以通过这种方式定义的,不过有些细节上的不同个,参见代码

class Teacher26base {
public:
	Teacher26base() {

	}

	void Teacher26basefun() {
		cout << "Teacher26basefun called" << endl;
	}
};

class Teacher26son : public Teacher26base {
public:
	Teacher26son() {

	}
	void Teacher26sonfun() {
		cout << "Teacher26sonfun called" << endl;
	}

	int static Teacher26sonstaticfunc(string str) {
		cout << "Teacher26sonstaticfunc called str = " << str << endl;
		return 10;
	}
};

void quanjuTeacher26() {
	cout << "全局quanjuTeacher26 called" << endl;
}

void main() {
	//旧的回忆,对于全局函数的访问,可以通过定义一个函数指针类型
	//1.定义一个 函数指针 类型 QuanJuFuncType
	typedef void(*QuanJuFuncType)();
	//2.使用类型  定义 变量 func,由于是一个指针,
	QuanJuFuncType func = quanjuTeacher26;
	func();

	//3.直接定义一个函数指针类型
	void(*jutiQuanJuFunc)();
	jutiQuanJuFunc = &quanjuTeacher26;
	//jutiQuanJuFunc = quanjuTeacher26;//对于全局函数,前面加不加&都一样
	jutiQuanJuFunc();

	//4.对于类成员函数,我们可以这么干吗,是的,也可以这么干,不过前面要加上类命名空间
	void(Teacher26son::*jutiQuanJuFuncTeacher26son)();

    //4.1 但是在类成员指针的赋值的时候,一定要加上&
	jutiQuanJuFuncTeacher26son = &Teacher26son::Teacher26sonfun;
	//4.2 调用时,需要一个类对象,//注意调用的时候,是(对象.*xxx)(),这是为了和类成员函数 tea.xxx区分
	Teacher26son tea;
	(tea.*jutiQuanJuFuncTeacher26son)();

	//5.如果是类静态成员函数
	int(*staticfunc)(string tempvalue) = &Teacher26son::Teacher26sonstaticfunc;//静态成员也是一样的,不同的是,需要加上类命名空间,注意的是和 全局函数一样,前面加不加 &都可以,加不加& 是历史原因。
	int result = staticfunc("abc");
	cout << "result = " << result  << endl;

}

二 指向虚成员函数的指针以及vcall进一步谈

如果要弄一个 指向类成员虚函数呢?

class Teacher26base {
public:
	Teacher26base() {

	}

	void Teacher26basefun() {
		cout << "Teacher26basefun called" << endl;
	}

	int virtual Teacher26basevirtualfunc(double dou) {
		cout << "Teacher26basevirtualfunc called dou = " << dou << endl;
		int inttemp = (int)dou;
		return inttemp;
	}

	int virtual Teacher26basevirtualfunc2(double dou) {
		cout << "Teacher26basevirtualfunc2 called dou = " << dou << endl;
		int inttemp = (int)dou * 2;
		return inttemp;
	}
};

class Teacher26son : public Teacher26base {
public:
	Teacher26son() {

	}
	void Teacher26sonfun() {
		cout << "Teacher26sonfun called" << endl;
	}

	int static Teacher26sonstaticfunc(string str) {
		cout << "Teacher26sonstaticfunc called str = " << str << endl;
		return 10;
	}

	int virtual Teacher26basevirtualfunc(double dou) {
		cout << "Teacher26sonvirtualfunc called dou = " << dou << endl;
		int inttemp = (int)dou;
		return inttemp;
	}

	int virtual Teacher26basevirtualfunc2(double dou) {
		cout << "Teacher26sonvirtualfunc2 called dou = " << dou << endl;
		int inttemp = (int)dou * 2;
		return inttemp;
	}

};

void quanjuTeacher26() {
	cout << "全局quanjuTeacher26 called" << endl;
}

void main() {
	//旧的回忆,对于全局函数的访问,可以通过定义一个函数指针类型
	//1.定义一个 函数指针 类型 QuanJuFuncType
	typedef void(*QuanJuFuncType)();
	//2.使用类型  定义 变量 func,由于是一个指针,
	QuanJuFuncType func = quanjuTeacher26;
	func();

	//3.直接定义一个函数指针类型
	void(*jutiQuanJuFunc)();
	jutiQuanJuFunc = &quanjuTeacher26;
	//jutiQuanJuFunc = quanjuTeacher26;//对于全局函数,前面加不加&都一样
	jutiQuanJuFunc();

	//对于类成员函数,我们可以这么干吗,是的,也可以这么干,不过前面要加上类命名空间
	void(Teacher26son::*jutiQuanJuFuncTeacher26son)();

	jutiQuanJuFuncTeacher26son = &Teacher26son::Teacher26sonfun;//但是在类成员指针的赋值的时候,一定要加上&
	//调用时,需要一个类对象,
	Teacher26son tea;
	(tea.*jutiQuanJuFuncTeacher26son)();//注意调用的时候,是(对象.*xxx)(),这是为了和类成员函数 tea.xxx区分


	//如果是静态成员函数
	int(*staticfunc)(string tempvalue) = &Teacher26son::Teacher26sonstaticfunc;//静态成员也是一样的,不同的是,需要加上类命名空间,注意的是和 全局函数一样,前面加不加 &都可以,加不加& 是历史原因。
	int result = staticfunc("abc");
	cout << "result = " << result  << endl;


	//5.如果是虚函数呢?由于虚函数也是依赖于 类对象调用的,也就是也需要this指针
	//  虚函数是如下的两个:int virtual Teacher26sonvirtualfunc(double dou) 
	//	int virtual Teacher26sonvirtualfunc2(double dou) 
	//5.1 定义如下
	int(Teacher26base::*vir)(double tempdou) = &Teacher26base::Teacher26basevirtualfunc;
	//5.2 使用 类指针 调用 虚函数指针类型
	Teacher26base *ptea26 = new Teacher26son();
	result = (ptea26->*vir)(3.14);
	
	
00A6F46E  mov         esi,esp  
00A6F470  sub         esp,8  
00A6F473  movsd       xmm0,mmword ptr [__real@40091eb851eb851f (0A79F78h)]  
00A6F47B  movsd       mmword ptr [esp],xmm0  
00A6F480  mov         ecx,dword ptr [ptea26]  
00A6F483  call        dword ptr [vir]  
00A6F486  cmp         esi,esp  
00A6F488  call        __RTC_CheckEsp (0A61596h)  
00A6F48D  mov         dword ptr [result],eax
	
	
	
	
	
	cout << "result1 = " << result << endl;
	//5.3 使用 类对象 调用  虚函数指针类型
	Teacher26base tea26;
	result = (tea26.*vir)(78.8);
	
	
	
	
	00A6F4D7  mov         esi,esp  
00A6F4D9  sub         esp,8  
00A6F4DC  movsd       xmm0,mmword ptr [__real@4053b33333333333 (0A79F98h)]  
00A6F4E4  movsd       mmword ptr [esp],xmm0  
00A6F4E9  lea         ecx,[tea26]  
00A6F4EC  call        dword ptr [vir]  
00A6F4EF  cmp         esi,esp  
00A6F4F1  call        __RTC_CheckEsp (0A61596h)  
00A6F4F6  mov         dword ptr [result],eax 





	cout << "result2 = " << result << endl;


	//5.4 通过 类指针 正常调用 虚函数
	ptea26->Teacher26basevirtualfunc(3.14);
	
	00A6F538  mov         esi,esp  
00A6F53A  sub         esp,8  
00A6F53D  movsd       xmm0,mmword ptr [__real@40091eb851eb851f (0A79F78h)]  
00A6F545  movsd       mmword ptr [esp],xmm0  
00A6F54A  mov         eax,dword ptr [ptea26]  
00A6F54D  mov         edx,dword ptr [eax]  
00A6F54F  mov         ecx,dword ptr [ptea26]  
00A6F552  mov         eax,dword ptr [edx]  
00A6F554  call        eax  
00A6F556  cmp         esi,esp  
00A6F558  call        __RTC_CheckEsp (0A61596h)  
	
	
	

	//5.5 通过 类对象 正常调用 虚函数
	tea26.Teacher26basevirtualfunc(23);
	
	00A6F55D  sub         esp,8  
00A6F560  movsd       xmm0,mmword ptr [__real@4037000000000000 (0A79F88h)]  
00A6F568  movsd       mmword ptr [esp],xmm0  
00A6F56D  lea         ecx,[tea26]  
00A6F570  call        Teacher26base::Teacher26basevirtualfunc (0A61442h)


	
	

	//5.6 通过类对象 正常调用普通函数
	tea26.Teacher26basefun();
	
	
	00A6F575  lea         ecx,[tea26]  
00A6F578  call        Teacher26base::Teacher26basefun (0A61749h)  





	//5.6 那么这个5.2,   5.3,   5.4有区别吗?debug看反汇编的代码
	cout << "断点在这里" << endl;
}

结论:

1. 不管是 类对象,还是 类指针 调用 虚函数指针 都是会走 虚函数指针 查找 虚函数表。

但是这个两种方式调用 虚函数的具体步骤 会有不同。

类指针 调用虚函数的时候,会使用 vptr 找虚函数表。

在使用函数指针调用成员虚函数的时候会使用到vcall。

//5.1 定义如下
	int(Teacher26base::*vir)(double tempdou) = &Teacher26base::Teacher26basevirtualfunc;
	//5.2 使用 类指针 调用 虚函数指针类型
	Teacher26base *ptea26 = new Teacher26son();
	result = (ptea26->*vir)(3.14);
	
	
00A6F46E  mov         esi,esp  
00A6F470  sub         esp,8  
00A6F473  movsd       xmm0,mmword ptr [__real@40091eb851eb851f (0A79F78h)]  
00A6F47B  movsd       mmword ptr [esp],xmm0  
00A6F480  mov         ecx,dword ptr [ptea26]  
00A6F483  call        dword ptr [vir]  
00A6F486  cmp         esi,esp  
00A6F488  call        __RTC_CheckEsp (0A61596h)  
00A6F48D  mov         dword ptr [result],eax
	
	
	
	
	
	cout << "result1 = " << result << endl;
	//5.3 使用 类对象 调用  虚函数指针类型
	Teacher26base tea26;
	result = (tea26.*vir)(78.8);
	
	
	
	
	00A6F4D7  mov         esi,esp  
00A6F4D9  sub         esp,8  
00A6F4DC  movsd       xmm0,mmword ptr [__real@4053b33333333333 (0A79F98h)]  
00A6F4E4  movsd       mmword ptr [esp],xmm0  
00A6F4E9  lea         ecx,[tea26]  
00A6F4EC  call        dword ptr [vir]  
00A6F4EF  cmp         esi,esp  
00A6F4F1  call        __RTC_CheckEsp (0A61596h)  
00A6F4F6  mov         dword ptr [result],eax 

看到:    result = (ptea26->*vir)(3.14); 通过 指向类虚函数指针的方法 调用该函数,也会有vcall的出现

00A6F4EC  call        dword ptr [vir]     //在这一步的时候,按F11 就会发现004D187F  jmp         Teacher26base::`vcall'{0}' (04D776Eh)  ,然后再F11,就会发现会走到这里:004D1799  jmp         Teacher26son::Teacher26basevirtualfunc (04D7C60h) 

2. 类指针 调用虚函数 会走 虚函数指针 查找 虚函数表

	//5.4 通过 类指针 正常调用 虚函数
	ptea26->Teacher26basevirtualfunc(3.14);
	
	00A6F538  mov         esi,esp  
00A6F53A  sub         esp,8  
00A6F53D  movsd       xmm0,mmword ptr [__real@40091eb851eb851f (0A79F78h)]  
00A6F545  movsd       mmword ptr [esp],xmm0  
00A6F54A  mov         eax,dword ptr [ptea26]  
00A6F54D  mov         edx,dword ptr [eax]  
00A6F54F  mov         ecx,dword ptr [ptea26]  
00A6F552  mov         eax,dword ptr [edx]  
00A6F554  call        eax  
00A6F556  cmp         esi,esp  
00A6F558  call        __RTC_CheckEsp (0A61596h)  

3.  类对象调用虚函数类对象调用 普通函数  会有不同个,但是都不会走

	//5.5 通过 类对象 正常调用 虚函数
	tea26.Teacher26basevirtualfunc(23);
	
	00A6F55D  sub         esp,8  
00A6F560  movsd       xmm0,mmword ptr [__real@4037000000000000 (0A79F88h)]  
00A6F568  movsd       mmword ptr [esp],xmm0  
00A6F56D  lea         ecx,[tea26]  
00A6F570  call        Teacher26base::Teacher26basevirtualfunc (0A61442h)


	
	

	//5.6 通过类对象 正常调用普通函数
	tea26.Teacher26basefun();
	
	
	00A6F575  lea         ecx,[tea26]  
00A6F578  call        Teacher26base::Teacher26basefun (0A61749h)  

4. 类对象调用虚函数没有 继承。

发现是和有继承的情况下是一样的。都是5步完成。

public:

	int virtual Teacher27virtualfunc(double dou) {
		cout << "Teacher27virtualfunc called dou = " << dou << endl;
		int inttemp = (int)dou;
		return inttemp;
	}
};

void main() {
	Teacher27 tea;
	tea.Teacher27virtualfunc(3.14);
	
	0028A37A  sub         esp,8  
0028A37D  movsd       xmm0,mmword ptr [__real@40091eb851eb851f (0299F78h)]  
0028A385  movsd       mmword ptr [esp],xmm0  
0028A38A  lea         ecx,[tea]  
0028A38D  call        Teacher27::Teacher27virtualfunc (0281906h) 


	cout << "end" << endl;
}

5. 在如下的代码中打印 虚函数的地址。

也会看到  vcall的使用

	printf("Teacher26base:: %p\n",&Teacher26base::Teacher26basevirtualfunc);
	
//	00E8F5C6  push        offset Teacher26base::`vcall'{0
//}' (0E8187Fh)  
//00E8F5CB  push        offset string "Teacher26base:: %p\n" (0E991E8h)
//00E8F5D0  call        _printf(0E81091h)
//00E8F5D5  add         esp, 8

总结:

什么时候会使用到vcall

在使用函数指针调用成员虚函数的时候会使用到vcall

三 vcall 在继承中的体现

    int(Teacher26base::*vir)(double tempdou) = &Teacher26base::Teacher26basevirtualfunc;
    int(Teacher26son::*vir1)(double tempdou) = &Teacher26son::Teacher26basevirtualfunc;

如上的两行代码,都是定义一个 函数指针,且是类的虚函数指针,到这里,我们应该有个意识,这两个里面都会使用到vcall代码段,vcall里面应该都存储的有 偏移,且由于Teacher26basevirtualfunc是第一个虚函数,因此偏移应该是0才对。

那么再调用的时候,我们一定要一个类指针,或者类引用

    Teacher26base *ptea26 = new Teacher26son();
    result = (ptea26->*vir)(3.14);
    cout << "result1 = " << result << endl;

指针和引用由于是动态绑定,因此等号右边是啥,ptea26就会指向的是啥。

因此当前case下,this就是 Teacher26son,有了this,有了偏移,就能找到对应的真正的函数,调用一下就可以找到真正的函数地址了。


    

你可能感兴趣的:(c++,学习,开发语言)