1、類的存儲(chǔ)空間
在INTEL 32 CPU,VC6環(huán)境下,空類的一個(gè)實(shí)例占一個(gè)字節(jié);
虛擬函數(shù)表指針占4個(gè)字節(jié)。
2、虛函數(shù)的實(shí)現(xiàn)過(guò)程
[網(wǎng)上很多講解, 本文有源代碼和部分匯編代碼]
3、虛擬析構(gòu)函數(shù)
無(wú)論基類的析構(gòu)函數(shù)是否為虛析構(gòu)函數(shù). 基類的析構(gòu)函數(shù)總是會(huì)被自動(dòng)調(diào)用的;
但是, 如果用基類指針去操作一個(gè)了派生類對(duì)象,
那么在delete這個(gè)基類指針時(shí),派生類的析構(gòu)函數(shù)將不會(huì)被調(diào)用.
4. 補(bǔ)充: 一個(gè)C++類本身,在內(nèi)存里是有信息的, 除了上面 虛函數(shù)表, 還有靜態(tài)成員變量。
VC6下的代碼:
// Test.cpp : Defines the entry point for the console application.
//
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include
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(); // 虛函數(shù)調(diào)用
delete pA;
}
pA->Fun()的匯編代碼如下:
59: pA->Fun();
0040D75C mov edx,dword ptr [ebp-10h] // edx為pA
0040D75F mov eax,dword ptr [edx] // eax為pA對(duì)象的虛表指針pVTable
0040D761 mov esi,esp
0040D763 mov ecx,dword ptr [ebp-10h] // this指針存入ecx
0040D766 call dword ptr [eax+4] // 函數(shù)地址:虛表指針+4, 就是虛表中第二項(xiàng)
0040D769 cmp esi,esp
0040D76B call __chkesp (00401b20)
1. 如果成員函數(shù)不是虛函數(shù),那么編譯的時(shí)候,就直接指定了調(diào)用函數(shù)的入口;
2. 如果是虛函數(shù),那么編譯時(shí)不直接指定函數(shù)入口,而是先在對(duì)象的內(nèi)存空間里取一個(gè)值(這個(gè)值就是虛函數(shù)表的地址,放在對(duì)象內(nèi)存空間的最前面4個(gè)字節(jié)里)。匯編代碼中會(huì)有取值的過(guò)程;
3. 虛函數(shù)表中按順序存放著虛函數(shù)在地址空間中的地址:的第一個(gè)DWORD存儲(chǔ)的就是第一個(gè)虛函數(shù)的地址,第二個(gè)DWORD存儲(chǔ)的就是第二個(gè)虛函數(shù)的地址;
3. 編譯器在編譯過(guò)程中已經(jīng)知道你調(diào)的那個(gè)函數(shù)在虛函數(shù)表中的序號(hào)。匯編代碼中會(huì)有體現(xiàn);
4. 在運(yùn)行時(shí),就能正確找到調(diào)用函數(shù)的地址,并調(diào)用它.
析構(gòu)函數(shù)的一點(diǎn)補(bǔ)充:
在一個(gè)項(xiàng)目中,如果有N層派生類,編譯器總是保證所有基類的析構(gòu)函數(shù)都被依次調(diào)用,但問(wèn)題是,究竟從那層開(kāi)始調(diào)用呢?對(duì)于非虛析構(gòu)函數(shù),顯然是在編譯期間就直接確定的,對(duì)虛析構(gòu)函數(shù),在運(yùn)行時(shí),才能確定是從哪一層開(kāi)始往下層調(diào)用(基類)。
事實(shí)上,和一般虛函數(shù)一樣,運(yùn)行時(shí),才確定要調(diào)用的析構(gòu)函數(shù),不過(guò)有些不同的是,析構(gòu)函數(shù)執(zhí)行完后,下一條指令就是基類析構(gòu)函數(shù)的CALL指令,一直到最上層為止。
下面是一段debug下的反匯編,其中Base派生自BaseBase.可以看到~Base調(diào)用后,會(huì)自動(dòng)調(diào)用~BaseBase
virtual Base::~Base() // 基類析構(gòu)函數(shù)
{
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) // 上層基類也緊跟著調(diào)用了
在INTEL 32 CPU,VC6環(huán)境下,空類的一個(gè)實(shí)例占一個(gè)字節(jié);
虛擬函數(shù)表指針占4個(gè)字節(jié)。
2、虛函數(shù)的實(shí)現(xiàn)過(guò)程
[網(wǎng)上很多講解, 本文有源代碼和部分匯編代碼]
3、虛擬析構(gòu)函數(shù)
無(wú)論基類的析構(gòu)函數(shù)是否為虛析構(gòu)函數(shù). 基類的析構(gòu)函數(shù)總是會(huì)被自動(dòng)調(diào)用的;
但是, 如果用基類指針去操作一個(gè)了派生類對(duì)象,
那么在delete這個(gè)基類指針時(shí),派生類的析構(gòu)函數(shù)將不會(huì)被調(diào)用.
4. 補(bǔ)充: 一個(gè)C++類本身,在內(nèi)存里是有信息的, 除了上面 虛函數(shù)表, 還有靜態(tài)成員變量。
VC6下的代碼:
// Test.cpp : Defines the entry point for the console application.
//
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include
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(); // 虛函數(shù)調(diào)用
delete pA;
}
pA->Fun()的匯編代碼如下:
59: pA->Fun();
0040D75C mov edx,dword ptr [ebp-10h] // edx為pA
0040D75F mov eax,dword ptr [edx] // eax為pA對(duì)象的虛表指針pVTable
0040D761 mov esi,esp
0040D763 mov ecx,dword ptr [ebp-10h] // this指針存入ecx
0040D766 call dword ptr [eax+4] // 函數(shù)地址:虛表指針+4, 就是虛表中第二項(xiàng)
0040D769 cmp esi,esp
0040D76B call __chkesp (00401b20)
1. 如果成員函數(shù)不是虛函數(shù),那么編譯的時(shí)候,就直接指定了調(diào)用函數(shù)的入口;
2. 如果是虛函數(shù),那么編譯時(shí)不直接指定函數(shù)入口,而是先在對(duì)象的內(nèi)存空間里取一個(gè)值(這個(gè)值就是虛函數(shù)表的地址,放在對(duì)象內(nèi)存空間的最前面4個(gè)字節(jié)里)。匯編代碼中會(huì)有取值的過(guò)程;
3. 虛函數(shù)表中按順序存放著虛函數(shù)在地址空間中的地址:的第一個(gè)DWORD存儲(chǔ)的就是第一個(gè)虛函數(shù)的地址,第二個(gè)DWORD存儲(chǔ)的就是第二個(gè)虛函數(shù)的地址;
3. 編譯器在編譯過(guò)程中已經(jīng)知道你調(diào)的那個(gè)函數(shù)在虛函數(shù)表中的序號(hào)。匯編代碼中會(huì)有體現(xiàn);
4. 在運(yùn)行時(shí),就能正確找到調(diào)用函數(shù)的地址,并調(diào)用它.
析構(gòu)函數(shù)的一點(diǎn)補(bǔ)充:
在一個(gè)項(xiàng)目中,如果有N層派生類,編譯器總是保證所有基類的析構(gòu)函數(shù)都被依次調(diào)用,但問(wèn)題是,究竟從那層開(kāi)始調(diào)用呢?對(duì)于非虛析構(gòu)函數(shù),顯然是在編譯期間就直接確定的,對(duì)虛析構(gòu)函數(shù),在運(yùn)行時(shí),才能確定是從哪一層開(kāi)始往下層調(diào)用(基類)。
事實(shí)上,和一般虛函數(shù)一樣,運(yùn)行時(shí),才確定要調(diào)用的析構(gòu)函數(shù),不過(guò)有些不同的是,析構(gòu)函數(shù)執(zhí)行完后,下一條指令就是基類析構(gòu)函數(shù)的CALL指令,一直到最上層為止。
下面是一段debug下的反匯編,其中Base派生自BaseBase.可以看到~Base調(diào)用后,會(huì)自動(dòng)調(diào)用~BaseBase
virtual Base::~Base() // 基類析構(gòu)函數(shù)
{
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) // 上層基類也緊跟著調(diào)用了