对象的构造与虚表
假设有这样的类
1 class Base 2 { 3 public: 4 Base() 5 { 6 clear(); 7 } 8 virtual ~Base() 9 { 10 int a = 0; 11 } 12 virtual void fun() 13 { 14 cout << "base" << endl; 15 } 16 void clear() 17 { 18 memset(this,0,sizeof Base); 19 } 20 int m_int32; 21 }; 22 class child : public Base 23 { 24 public: 25 virtual ~child() 26 { 27 int a = 0; 28 } 29 virtual void fun() 30 { 31 cout << "child" << endl; 32 } 33 };
vs会把父类,子类的虚表地址存在代码段
每次构造函数和析构函数调用之前,会把各自的虚表地址设回去,
父类析构:
1 00411B36 mov dword ptr [eax],offset Base::`vftable' (417814h) 2 int a = 0; 3 00411B3C mov dword ptr [a],0
父类构造:
00411976 mov dword ptr [eax],offset Base::`vftable' (417814h) { clear(); 0041197C mov ecx,dword ptr [this] 0041197F call Base::clear (411127h) }
子类构造:
00411916 call Base::Base (411145h) 0041191B mov eax,dword ptr [this] 0041191E mov dword ptr [eax],offset child::`vftable' (417808h) 00411924 mov eax,dword ptr [this]
可得出以下结论:
当new 一个子类时,编译器已经把父类及子类的虚表入口地址保存在代码段中,父类及子类各有一份虚表,也已经在构造之前赋值(子类前面有一些值还不清楚)。
this指针保存的是这个新建类的地址,头4个字节存放虚表的入口地址,先构造父类,把父类的虚表入口地址赋值。按照上面程序例子,之后把父类大小的区域置0。在构造子类,把子类的虚表入口地址写入。
析构的时候,自底向上析构,编译器会先把本类的虚表入口地址赋值,这样做是为了正确的析构本类,因为如果不重新赋值,那就可能调用子类的函数,而此时子类已经析构完成了。
在构造和析构函数中,不能使用虚函数,原因就是每次构造和析构,编译器都会在之前把本类的虚函数入口值重新设置,调用虚函数是没有意义的