1. 子类是怎样调用父类的成员函数的?即this指针是怎样传递的,需要移动位置吗?
********************************************************************************************
答:继承分实继承和虚继承两类。虚继承是为了解决棱形继承时祖父类对象不唯一而提出的继承方式。
实继承:
子类,父类,祖父类其内存分配为先祖父类,父类(包含祖父类),子类(包含祖父及父类)
三类都共用一个this 指针,
所以传递给父类及祖父类的指针与子类是一样的。
--------------------------------------------------------------------------------------------
举例:
#include <iostream>
using namespace std;
class A
{
public:
A():i(1){
}
void testa(){
cout <<"test a "<<i<<endl;
}
int i;
};
class B : public A
{
public:
B():j(2){
}
void testb(){
cout <<"test b "<<j<<endl;
}
int j;
};
class C :public B
{
public:
C():k(3){
}
void testc(){
cout <<"test c "<<k<<endl;
}
int k;
};
int main()
{
C c;
c.testa();
c.testb();
c.testc();
system("pause");
return 0;
}
内存布局:实继承
0013FF74 01 00 00 00 .... 祖父类
0013FF78 02 00 00 00 .... 父类
0013FF7C 03 00 00 00 .... 子类
--------------------------------------------------------------------------------------------
构造函数构造过程。
是递归调用,而且构造的this 一直都没有改变。基类返回后,派生类再构造自己的变量。
代码就不贴了。
--------------------------------------------------------------------------------------------
类函数call 时的this 传递, 可以看到传递均为子类的this 指针,没有变化
37: c.testa();
004017F0 lea ecx,[ebp-0Ch] //this 指针
004017F3 call @ILT+520(A::testa) (0040120d)
38: c.testb();
004017F8 lea ecx,[ebp-0Ch] //this 指针,没有变化
004017FB call @ILT+445(B::testb) (004011c2)
39: c.testc();
00401800 lea ecx,[ebp-0Ch] //this 指针,没有变化
00401803 call @ILT+210(C::testc) (004010d7)
********************************************************************************************
虚继承。
子类,父类,祖父类其内存分配为先子类,再父类,再祖父类。保证基类只有一份。vc6下有一个
额外指针vbptr. 指向各父类基址相对于子类基址的偏移。
三类的this 指针已经指向了三处位置。所以当调用父类,祖父类函数时,需要移动this 指针。
********************************************************************************************
举例: 将上例继承关系改为虚继承。
1. 内存布局
0013FF6C 48 00 47 00 H.G. -》 08 00 00 00, 0C 00 00 00
0013FF70 03 00 00 00 .... // 子类
0013FF74 01 00 00 00 .... // 祖父类
0013FF78 3C 00 47 00 <.G. -》 FC FF FF FF (-4)
0013FF7C 02 00 00 00 .... // 父类
2. 成员函数调用过程。
37: c.testa();
004017F2 mov eax,dword ptr [ebp-14h] // 取到vbptr
004017F5 mov ecx,dword ptr [eax+4] // 取到偏移量
004017F8 lea ecx,[ebp+ecx-14h] // 调整为A类对象地址
004017FC call @ILT+520(A::testa) (0040120d) 调用成员函数
38: c.testb();
00401801 mov edx,dword ptr [ebp-14h] //取到vbptr 指针
00401804 mov eax,dword ptr [edx+8] // 取到类B对象偏移
00401807 lea ecx,[ebp+eax-14h] // 调整为B类对象地址
0040180B call @ILT+445(B::testb) (004011c2) 调用成员函数
39: c.testc();
00401810 lea ecx,[ebp-14h] // 子类this指针
00401813 call @ILT+210(C::testc) (004010d7) 调用成员函数
3. 构造函数的构造过程。
004019B3 mov eax,dword ptr [ebp-4] // 构造该对象布局
004019B6 mov dword ptr [eax],offset C::`vbtable' (00470048)
004019BC mov ecx,dword ptr [ebp-4]
004019BF mov dword ptr [ecx+0Ch],offset C::`vbtable' (0047003c)
004019C6 mov ecx,dword ptr [ebp-4]
004019C9 add ecx,8 // 调整指针
004019CC call @ILT+390(A::A) (0040118b) // 调用祖父类构造
004019D1 push 0 // 通知父类不要构建布局
004019D3 mov ecx,dword ptr [ebp-4]
004019D6 add ecx,0Ch // 调整指针
004019D9 call @ILT+380(B::B) (00401181) // 调用父类构造
a.填充2个vbtable 指针。目前,举例中只看到使用第一项。然后调整指针,调用A类构造。
再调整指针,调用B类构造。注意。B类构造先push 0, 何也?
这个0 是告诉一个执行流程,不要再初始化vbptr 及其基类,只初始化自己的变量即可。
A类是最基本类,所以它不需要vbptr ,故不用push 0.
即如此,我们把B类构造也copy 来吧,以便看清B类初始化Vbptr 的流程。
00401A7A mov dword ptr [ebp-4],ecx
00401A7D cmp dword ptr [ebp+8],0 // 比较push 进来的参数是0 还是 1
00401A81 je B::B+37h (00401a97) // 为0,不要初始化本类的vbptr 及其子类
// 以下是B类对象布局的初始化
00401A83 mov eax,dword ptr [ebp-4] // 取到this 指针
00401A86 mov dword ptr [eax],offset B::`vbtable' (00470058) //填充第一项VTptr
00401A8C mov ecx,dword ptr [ebp-4]
00401A8F add ecx,8 // 获得A类的this 指针。
00401A92 call @ILT+390(A::A) (0040118b) //调用构造
00401A97 mov ecx,dword ptr [ebp-4] //非B类初始化时,直接跳转到此,完成B数据初始化
00401A9A mov dword ptr [ecx+4],2
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/hejinjing_tom_com/archive/2009/05/10/4165462.aspx