1、类的存储空间 在INTEL 32 CPU,VC6环境下,空类的一个实例占一个字节; 虚拟函数表指针占4个字节。 2、虚函数的实现过程 [网上很多讲解, 本文有源代码和部分汇编代码] 3、虚拟析构函数 无论基类的析构函数是否为虚析构函数. 基类的析构函数总是会被自动调用的; 但是, 如果用基类指针去操作一个了派生类对象, 那么在delete这个基类指针时,派生类的析构函数将不会被调用. 4. 补充: 一个C++类本身,在内存里是有信息的, 除了上面 虚函数表, 还有静态成员变量。 VC6下的代码: // Test.cpp : Defines the entry point for the console application. // #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #include <stdio.h> class Base; class Derived; void GFunction(void); int main(int argc, char* argv[]) { GFunction(); char a = 127; a+=1; printf("new a = %d\n", a); getchar(); return 0; } class Base { public: Base::Base() { }; virtual Base::~Base() { printf("Base deconstruct\n"); }; virtual void Fun() { }; int a ; }; class Derived : public Base { public: Derived::Derived() { }; virtual Derived::~Derived() { printf("Derived deconstruct\n"); }; virtual void Fun() { }; }; void GFunction(void) { printf("Class Base Sizeof =%d \n", sizeof(Base)); printf("Class Derived Sizeof =%d \n\n", sizeof(Derived)); Base* pA = (Base*)new Derived; pA->Fun(); // 虚函数调用 delete pA; } pA->Fun()的汇编代码如下: 59: pA->Fun(); 0040D75C mov edx,dword ptr [ebp-10h] // edx为pA 0040D75F mov eax,dword ptr [edx] // eax为pA对象的虚表指针pVTable 0040D761 mov esi,esp 0040D763 mov ecx,dword ptr [ebp-10h] // this指针存入ecx 0040D766 call dword ptr [eax+4] // 函数地址:虚表指针+4, 就是虚表中第二项 0040D769 cmp esi,esp 0040D76B call __chkesp (00401b20) 1. 如果成员函数不是虚函数,那么编译的时候,就直接指定了调用函数的入口; 2. 如果是虚函数,那么编译时不直接指定函数入口,而是先在对象的内存空间里取一个值(这个值就是虚函数表的地址,放在对象内存空间的最前面4个字节里)。汇编代码中会有取值的过程; 3. 虚函数表中按顺序存放着虚函数在地址空间中的地址:的第一个DWORD存储的就是第一个虚函数的地址,第二个DWORD存储的就是第二个虚函数的地址; 3. 编译器在编译过程中已经知道你调的那个函数在虚函数表中的序号。汇编代码中会有体现; 4. 在运行时,就能正确找到调用函数的地址,并调用它. 析构函数的一点补充: 在一个项目中,如果有N层派生类,编译器总是保证所有基类的析构函数都被依次调用,但问题是,究竟从那层开始调用呢?对于非虚析构函数,显然是在编译期间就直接确定的,对虚析构函数,在运行时,才能确定是从哪一层开始往下层调用(基类)。 事实上,和一般虚函数一样,运行时,才确定要调用的析构函数,不过有些不同的是,析构函数执行完后,下一条指令就是基类析构函数的CALL指令,一直到最上层为止。 下面是一段debug下的反汇编,其中Base派生自BaseBase.可以看到~Base调用后,会自动调用~BaseBase virtual Base::~Base() // 基类析构函数 { 00401740 push ebp 00401741 mov ebp,esp 00401743 sub esp,0CCh 00401749 push ebx 0040174A push esi 0040174B push edi 0040174C push ecx 0040174D lea edi,[ebp-0CCh] 00401753 mov ecx,33h 00401758 mov eax,0CCCCCCCCh 0040175D rep stos dword ptr es:[edi] 0040175F pop ecx 00401760 mov dword ptr [ebp-8],ecx 00401763 mov eax,dword ptr [this] 00401766 mov dword ptr [eax],offset Base::`vftable' (44533Ch) printf("Base deconstruct\n"); 0040176C push offset string "Base deconstruct\n" (445344h) 00401771 call printf (405760h) 00401776 add esp,4 }; 00401779 mov ecx,dword ptr [this] 0040177C call BaseBase::~BaseBase (4017A0h) // 上层基类也紧跟着调用了
|