首页 > VC++ > 关于基类构造函数调用虚函数实际调用的不是派生类的问题的原因

关于基类构造函数调用虚函数实际调用的不是派生类的问题的原因

2012年7月1日 发表评论 阅读评论 1252次阅读    

关于基类构造函数调用虚函数实际调用的不是派生类的问题的原因

我们知道,类的构造函数里面编译器插入了很多代码,比如异常安全,虚函数表指针的设置,基类构造,等等。

而且,关键是这些代码时在任何用户的代码(非初始化)的地方之前插入的,问题就来了···

如果在基类构造函数里面调用基类的虚函数,那么,实际调用的却不像我们当初认为的多态效果,为什么呢?

下面看看编译怎么实现的就知道了····

下面是测试类:

class A {
public:
	A() {
		vfunc() ;
	}
	virtual vfunc() {
		;
	}
} ;

class CC : public A   {
public:
	virtual vfunc() {
		;
	}
};

int main()
{
	CC c  ;
	return  0 ;
}

原因:

 004012F8   lea         ecx,[ebp-4]	//装载this指针到ECX,这是thiscall的调研约定
004012FB   call        @ILT+5(CC::CC) (0040100a)  //调用CC的构造函数
30:       return  0 ;
00401300   xor         eax,eax
31:   }
///////////////////////////////////////////////////////////////////////

CC::CC:
····
00401339   pop         ecx		//this指针到ECX
0040133A   mov         dword ptr [ebp-4],ecx
0040133D   mov         ecx,dword ptr [ebp-4]
00401340   call        @ILT+15(A::A) (00401014)	//派生类构造函数进入后,完全没干什么事就“马上”调用了基类的构造函数,那么,其vptr虚函数表指针也没有赋值!
00401345   mov         eax,dword ptr [ebp-4]
00401348   mov         dword ptr [eax],offset CC::`vftable' (00431068)//直到这才对自己的虚函数表指针赋值,因此,此前的调用,绝不会涉及到派生类CC的虚函数··
0040134E   mov         eax,dword ptr [ebp-4]   //此后如果有任何虚函数调用,访问虚函数表时访问的就正常的是派生类的虚函数表了,调用的也正确了
00401351   pop         edi
00401352   pop         esi
00401353   pop         ebx
00401354   add         esp,44h
00401357   cmp         ebp,esp
00401359   call        __chkesp (00401590)
0040135E   mov         esp,ebp
00401360   pop         ebp
00401361   ret
///////////////////////////////////////////////////////////////////////

10:       A() {
····
00401399   pop         ecx
0040139A   mov         dword ptr [ebp-4],ecx
0040139D   mov         eax,dword ptr [ebp-4]
004013A0   mov         dword ptr [eax],offset A::`vftable' (0043106c)	//注意到,基类第一件事情是:为虚函数表指针地址赋值,
								//此指针传下来其实是CC派生类的this指针的地址,也就是:类的第一个字节
11:           vfunc() ;//此时调用的时候,理所当然会传入this指针,此this的vptr此时还不是派生类的,而是上面一句设置的基类的虚函数表地址!
004013A6   mov         ecx,dword ptr [ebp-4]	//传递这个很多人都会“错误的以为的”this
004013A9   call        @ILT+10(A::vfunc) (0040100f) //调用这个“虚函数”,这里没有访问虚函数表是因为编译器优化的原因吧,因为此时肯定为A的vfunc,
						//感兴趣可以试一下:看下面的例子
12:       }
·····
004013C1   ret

上面没有访问虚函数表,是因为编译器优化了一下,如果我们的A类构造函数是这样的,那么···

 class A {
public:
	A() {
		init() ;//先调用A的非虚函数,里面再调用vfunc虚函数就可以看出要访问虚函数表了,不过此时的虚函数表指针指向A的虚函数表
	}
	virtual vir() { ;} ;//故意加了一项,待会会看到+4的字样
	virtual vfunc() {//应该在虚函数表的第二项
		;
	}
	init() {
		vfunc() ;
	}
} ;

10:       A() {
····
004013A9   pop         ecx
004013AA   mov         dword ptr [ebp-4],ecx
004013AD   mov         eax,dword ptr [ebp-4]
004013B0   mov         dword ptr [eax],offset A::`vftable' (00431074)//设置此时的虚函数表指针为基类的!!不是派生类的,调用完成后会被派生类覆盖
11:           init() ;
004013B6   mov         ecx,dword ptr [ebp-4]
004013B9   call        @ILT+40(A::init) (0040102d)	//不是虚函数,直接调用,不用访问虚函数表
12:       }
····
004013D1   ret

///////////////////////////////////////////////////////////////////////
18:       init() {
····
00401469   pop         ecx
0040146A   mov         dword ptr [ebp-4],ecx //this指针放到栈顶
19:           vfunc() ;
0040146D   mov         eax,dword ptr [ebp-4]  //下面准备虚函数表的查询,可以看出,虚函数调用机制还是会多用几次寻址操作的,访问内存。
00401470   mov         edx,dword ptr [eax]   //this-》edx。其实其内容是A的虚函数表的指针
00401472   mov         esi,esp
00401474   mov         ecx,dword ptr [ebp-4]
00401477   call        dword ptr [edx+4]	//vfunc在第二项,所以+4,这里
0040147A   cmp         esi,esp
0040147C   call        __chkesp (00401610)
20:       }
····
00401491   ret
Share
分类: VC++ 标签: , ,
  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.

注意: 评论者允许使用'@user空格'的方式将自己的评论通知另外评论者。例如, ABC是本文的评论者之一,则使用'@ABC '(不包括单引号)将会自动将您的评论发送给ABC。使用'@all ',将会将评论发送给之前所有其它评论者。请务必注意user必须和评论者名相匹配(大小写一致)。